Compare commits

..
Author SHA1 Message Date
Josh Patterson 9ad632a63a Merge pull request #16105 from Security-Onion-Solutions/fixESHEAP
Add default esheap value so nodes highstate without the pillar value
2026-07-23 16:02:49 -04:00
Josh Patterson e2fc9e0325 Merge pull request #16094 from Security-Onion-Solutions/saltthangs
Saltthangs
2026-07-22 08:44:08 -04:00
Josh Patterson ce0095154b Merge pull request #16082 from Security-Onion-Solutions/saltthangs
add date and time to output
2026-07-17 16:10:47 -04:00
Josh Patterson 7b58c67a02 Merge pull request #16080 from Security-Onion-Solutions/saltthangs
Saltthangs
2026-07-17 11:55:09 -04:00
Josh Patterson 28b3a54a27 Merge pull request #16077 from Security-Onion-Solutions/saltthangs
so-salt-minion-wait: report ready immediately when already ready
2026-07-17 10:14:55 -04:00
Josh Patterson 403d846a89 Merge pull request #16076 from Security-Onion-Solutions/saltthangs
Saltthangs
2026-07-16 18:10:50 -04:00
Josh Patterson 33ed6359bb Merge pull request #16072 from Security-Onion-Solutions/saltthangs
Saltthangs
2026-07-15 15:31:08 -04:00
156 changed files with 865 additions and 7064 deletions

No files matched your search

-2
View File
@@ -12,8 +12,6 @@ body:
- 3.0.0 - 3.0.0
- 3.1.0 - 3.1.0
- 3.2.0 - 3.2.0
- 3.3.0
- 3.4.0
- Other (please provide detail below) - Other (please provide detail below)
validations: validations:
required: true required: true
-25
View File
@@ -6,9 +6,6 @@ on:
- "salt/sensoroni/files/analyzers/**" - "salt/sensoroni/files/analyzers/**"
- "salt/manager/tools/sbin/**" - "salt/manager/tools/sbin/**"
- "salt/_beacons/**" - "salt/_beacons/**"
- "salt/telegraf/tools/sbin_jinja/**"
- "salt/telegraf/defaults.yaml"
- "salt/telegraf/soc_telegraf.yaml"
jobs: jobs:
build: build:
@@ -37,25 +34,3 @@ jobs:
- name: Test with pytest - name: Test with pytest
run: | run: |
PYTHONPATH=${{ matrix.python-code-path }} pytest ${{ matrix.python-code-path }} --cov=${{ matrix.python-code-path }} --doctest-modules --cov-report=term --cov-fail-under=100 --cov-config=pytest.ini PYTHONPATH=${{ matrix.python-code-path }} pytest ${{ matrix.python-code-path }} --cov=${{ matrix.python-code-path }} --doctest-modules --cov-report=term --cov-fail-under=100 --cov-config=pytest.ini
telegraf-collector:
# so-container-stats is a jinja template rather than an importable module, so it gets its
# own job: the test renders it the way salt does, then drives it with a faked docker engine
# and cgroup tree. No container runtime is needed.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: "3.14"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install flake8 pytest jinja2 pyyaml
- name: Lint with flake8
run: |
flake8 salt/telegraf/tools/sbin_jinja/so-container-stats_test.py --config=pytest.ini
- name: Test with pytest
run: |
pytest salt/telegraf/tools/sbin_jinja/so-container-stats_test.py -v
+11 -11
View File
@@ -1,17 +1,17 @@
### 3.3.0-20260911 ISO image released on 2026/09/11 ### 3.1.0-20260528 ISO image released on 2026/05/28
### Download and Verify ### Download and Verify
3.3.0-20260911 ISO image: 3.1.0-20260528 ISO image:
https://download.securityonion.net/file/securityonion/securityonion-3.3.0-20260911.iso https://download.securityonion.net/file/securityonion/securityonion-3.1.0-20260528.iso
MD5: 12B18433D3A2198A185892FF79CF638F MD5: 9D6FF58DEEE24089D722C73169765B3E
SHA1: 2B3C2E1FA7A78ED1F956E7EDCC12E32593C14EEE SHA1: 2B8B816B6CEC3B7F96B3C5E040EBF502DD2C412F
SHA256: 0938C73B76CE30EC9E4394D312C79EA7CAC721B6818541697279A6221F7D870D SHA256: 62FAB57E247C843D6A04F0796D8162C732B65D82FC3E4A59D087135B9FD32912
Signature for ISO image: Signature for ISO image:
https://github.com/Security-Onion-Solutions/securityonion/raw/3/main/sigs/securityonion-3.3.0-20260911.iso.sig https://github.com/Security-Onion-Solutions/securityonion/raw/3/main/sigs/securityonion-3.1.0-20260528.iso.sig
Signing key: Signing key:
https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/3/main/KEYS https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/3/main/KEYS
@@ -25,22 +25,22 @@ wget https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/3/
Download the signature file for the ISO: Download the signature file for the ISO:
``` ```
wget https://github.com/Security-Onion-Solutions/securityonion/raw/3/main/sigs/securityonion-3.3.0-20260911.iso.sig wget https://github.com/Security-Onion-Solutions/securityonion/raw/3/main/sigs/securityonion-3.1.0-20260528.iso.sig
``` ```
Download the ISO image: Download the ISO image:
``` ```
wget https://download.securityonion.net/file/securityonion/securityonion-3.3.0-20260911.iso wget https://download.securityonion.net/file/securityonion/securityonion-3.1.0-20260528.iso
``` ```
Verify the downloaded ISO image using the signature file: Verify the downloaded ISO image using the signature file:
``` ```
gpg --verify securityonion-3.3.0-20260911.iso.sig securityonion-3.3.0-20260911.iso gpg --verify securityonion-3.1.0-20260528.iso.sig securityonion-3.1.0-20260528.iso
``` ```
The output should show "Good signature" and the Primary key fingerprint should match what's shown below: The output should show "Good signature" and the Primary key fingerprint should match what's shown below:
``` ```
gpg: Signature made Fri 11 Sep 2026 11:23:56 AM EDT using RSA key ID FE507013 gpg: Signature made Wed 27 May 2026 03:03:59 PM EDT using RSA key ID FE507013
gpg: Good signature from "Security Onion Solutions, LLC <info@securityonionsolutions.com>" gpg: Good signature from "Security Onion Solutions, LLC <info@securityonionsolutions.com>"
gpg: WARNING: This key is not certified with a trusted signature! gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner. gpg: There is no indication that the signature belongs to the owner.
+1 -1
View File
@@ -1 +1 @@
3.4.0 3.2.0
-221
View File
@@ -1,221 +0,0 @@
# 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.
import hashlib
import os
import shutil
import tempfile
import unittest
from unittest.mock import patch
import local_files_beacon
class TestRulesBeacon(unittest.TestCase):
def setUp(self):
# Isolate all on-disk state (watermarks and the dirs we fingerprint) in a
# throwaway tree, and point WATERMARK_DIR at it so the real read/write
# helpers run against actual files.
self.tmpdir = tempfile.mkdtemp()
self.state = os.path.join(self.tmpdir, 'state')
patcher = patch.object(local_files_beacon, 'WATERMARK_DIR', self.state)
patcher.start()
self.addCleanup(patcher.stop)
def tearDown(self):
shutil.rmtree(self.tmpdir, ignore_errors=True)
def _make_dir(self, name, files=None):
path = os.path.join(self.tmpdir, name)
os.makedirs(path, exist_ok=True)
for fname, content in (files or {}).items():
with open(os.path.join(path, fname), 'w') as f:
f.write(content)
return path
# -- trivial contract -------------------------------------------------
def test_virtual_returns_true(self):
self.assertTrue(local_files_beacon.__virtual__())
def test_validate_returns_valid(self):
self.assertEqual(local_files_beacon.validate({}), (True, 'valid'))
# -- _paths_from_config -----------------------------------------------
def test_paths_from_config_list_of_dicts(self):
config = [{'interval': 10}, {'paths': {'/a': 'suricata', '/b': 'strelka'}}]
self.assertEqual(
local_files_beacon._paths_from_config(config),
{'/a': 'suricata', '/b': 'strelka'},
)
def test_paths_from_config_plain_dict(self):
self.assertEqual(
local_files_beacon._paths_from_config({'paths': {'/a': 'suricata'}}),
{'/a': 'suricata'},
)
def test_paths_from_config_skips_non_dict_items(self):
self.assertEqual(local_files_beacon._paths_from_config(['bogus', 42]), {})
def test_paths_from_config_paths_not_a_dict(self):
self.assertEqual(local_files_beacon._paths_from_config({'paths': 'nope'}), {})
def test_paths_from_config_unexpected_type(self):
self.assertEqual(local_files_beacon._paths_from_config('nonsense'), {})
# -- _excluded --------------------------------------------------------
def test_excluded_matches_temp_and_editor_files(self):
for pathname in ('/rules/foo.swp', '/rules/foo~', '/rules/4913', '/rules/.#foo'):
self.assertTrue(local_files_beacon._excluded(pathname), pathname)
def test_excluded_allows_real_rule_files(self):
self.assertFalse(local_files_beacon._excluded('/rules/suricata.rules'))
# -- _fingerprint -----------------------------------------------------
def test_fingerprint_missing_dir_is_empty_tree_digest(self):
missing = os.path.join(self.tmpdir, 'does-not-exist')
self.assertEqual(local_files_beacon._fingerprint(missing), hashlib.sha1().hexdigest())
def test_fingerprint_changes_when_content_changes(self):
d = self._make_dir('rules', {'a.rules': 'alert'})
before = local_files_beacon._fingerprint(d)
with open(os.path.join(d, 'a.rules'), 'w') as f:
f.write('alert tcp any any -> any any') # different size
self.assertNotEqual(local_files_beacon._fingerprint(d), before)
def test_fingerprint_ignores_excluded_files(self):
d = self._make_dir('rules', {'a.rules': 'alert'})
before = local_files_beacon._fingerprint(d)
with open(os.path.join(d, 'a.rules.swp'), 'w') as f:
f.write('editor swap')
self.assertEqual(local_files_beacon._fingerprint(d), before)
def test_fingerprint_skips_unstatable_entries(self):
# A dangling symlink appears in os.walk's file list but os.stat raises
# OSError, exercising the except-continue path.
d = self._make_dir('rules', {'a.rules': 'alert'})
good = local_files_beacon._fingerprint(d)
os.symlink(os.path.join(d, 'missing-target'), os.path.join(d, 'broken.link'))
self.assertEqual(local_files_beacon._fingerprint(d), good)
def test_fingerprint_prunes_git_metadata(self):
# zkg packages are git clones, so the watched tree carries .git.
d = self._make_dir('zkg', {'pkg.zeek': 'print 1;'})
before = local_files_beacon._fingerprint(d)
git_dir = os.path.join(d, 'pkg', '.git', 'refs', 'heads')
os.makedirs(git_dir)
with open(os.path.join(git_dir, 'main'), 'w') as f:
f.write('0' * 40)
self.assertEqual(local_files_beacon._fingerprint(d), before)
def test_fingerprint_still_sees_worktree_next_to_git(self):
d = self._make_dir('zkg', {'pkg.zeek': 'print 1;'})
os.makedirs(os.path.join(d, 'pkg', '.git'))
before = local_files_beacon._fingerprint(d)
with open(os.path.join(d, 'pkg', 'scripts.zeek'), 'w') as f:
f.write('print 2;')
self.assertNotEqual(local_files_beacon._fingerprint(d), before)
# -- _read_watermark / _write_watermark -------------------------------
def test_watermark_round_trip(self):
local_files_beacon._write_watermark('suricata', '/rules/suricata', 'deadbeef')
self.assertEqual(
local_files_beacon._read_watermark('suricata', '/rules/suricata'), 'deadbeef')
def test_read_watermark_missing_returns_none(self):
self.assertIsNone(local_files_beacon._read_watermark('suricata', '/rules/suricata'))
def test_read_watermark_empty_file_returns_none(self):
os.makedirs(self.state, exist_ok=True)
with open(local_files_beacon._watermark_file('suricata', '/rules/suricata'), 'w') as f:
f.write('')
self.assertIsNone(local_files_beacon._read_watermark('suricata', '/rules/suricata'))
def test_write_watermark_swallows_oserror(self):
with patch.object(local_files_beacon.os, 'makedirs', side_effect=OSError):
local_files_beacon._write_watermark('suricata', '/rules/suricata', 'deadbeef')
self.assertIsNone(local_files_beacon._read_watermark('suricata', '/rules/suricata'))
def test_watermark_file_differs_per_directory_within_one_tag(self):
# zeek/policy and zeek/zkg share the tag 'zeek'.
self.assertNotEqual(
local_files_beacon._watermark_file('zeek', '/local/zeek/policy'),
local_files_beacon._watermark_file('zeek', '/local/zeek/zkg'),
)
def test_watermarks_are_independent_within_one_tag(self):
local_files_beacon._write_watermark('zeek', '/local/zeek/policy', 'policyhash')
local_files_beacon._write_watermark('zeek', '/local/zeek/zkg', 'zkghash')
self.assertEqual(
local_files_beacon._read_watermark('zeek', '/local/zeek/policy'), 'policyhash')
self.assertEqual(
local_files_beacon._read_watermark('zeek', '/local/zeek/zkg'), 'zkghash')
# -- beacon -----------------------------------------------------------
def _config(self, mapping):
return [{'paths': mapping}]
def test_beacon_seeds_first_run_and_emits_nothing(self):
with patch.object(local_files_beacon, '_fingerprint', return_value='hash1'), \
patch.object(local_files_beacon, '_read_watermark', return_value=None), \
patch.object(local_files_beacon, '_write_watermark') as mock_write:
result = local_files_beacon.beacon(self._config({'/rules/suricata': 'suricata'}))
self.assertEqual(result, [])
mock_write.assert_called_once_with('suricata', '/rules/suricata', 'hash1')
def test_beacon_emits_on_change(self):
with patch.object(local_files_beacon, '_fingerprint', return_value='newhash'), \
patch.object(local_files_beacon, '_read_watermark', return_value='oldhash'), \
patch.object(local_files_beacon, '_write_watermark') as mock_write:
result = local_files_beacon.beacon(self._config({'/rules/suricata': 'suricata'}))
self.assertEqual(result, [{'tag': 'suricata', 'path': '/rules/suricata'}])
mock_write.assert_called_once_with('suricata', '/rules/suricata', 'newhash')
def test_beacon_no_change_emits_nothing(self):
with patch.object(local_files_beacon, '_fingerprint', return_value='samehash'), \
patch.object(local_files_beacon, '_read_watermark', return_value='samehash'), \
patch.object(local_files_beacon, '_write_watermark') as mock_write:
result = local_files_beacon.beacon(self._config({'/rules/suricata': 'suricata'}))
self.assertEqual(result, [])
mock_write.assert_not_called()
def test_beacon_end_to_end_with_real_files(self):
# Exercise the full stack (real fingerprint + real watermark files) across
# two poll passes: first seeds silently, second fires after a write.
d = self._make_dir('rules', {'a.rules': 'alert'})
config = self._config({d: 'suricata'})
self.assertEqual(local_files_beacon.beacon(config), []) # seed pass
self.assertEqual(local_files_beacon.beacon(config), []) # unchanged pass
with open(os.path.join(d, 'b.rules'), 'w') as f:
f.write('alert tcp any any -> any any')
self.assertEqual(local_files_beacon.beacon(config), [{'tag': 'suricata', 'path': d}])
def test_beacon_two_dirs_one_tag_do_not_flap(self):
# Tag-keyed watermarks would clobber each other and emit on every pass.
policy = self._make_dir('zeek/policy', {'intel.dat': '#fields\tindicator'})
zkg = self._make_dir('zeek/zkg', {'README': 'place packages here'})
config = self._config({policy: 'zeek', zkg: 'zeek'})
self.assertEqual(local_files_beacon.beacon(config), []) # seed pass
self.assertEqual(local_files_beacon.beacon(config), []) # idle
self.assertEqual(local_files_beacon.beacon(config), []) # still idle
with open(os.path.join(policy, 'intel.dat'), 'a') as f:
f.write('\nevil.com\tIntel::DOMAIN\tsource\n')
self.assertEqual(local_files_beacon.beacon(config), [{'tag': 'zeek', 'path': policy}])
self.assertEqual(local_files_beacon.beacon(config), []) # quiet again
if __name__ == '__main__':
unittest.main()
-2
View File
@@ -131,8 +131,6 @@ def beacon(config): # noqa: C901
'setting_id': setting_id, 'setting_id': setting_id,
'node_id': node_id, 'node_id': node_id,
}) })
log.info('postgres_pillar_beacon: audit_settings id=%d setting_id=%s node_id=%s',
row_id, setting_id, node_id)
if row_id > max_id: if row_id > max_id:
max_id = row_id max_id = row_id
@@ -3,14 +3,13 @@
# https://securityonion.net/license; you may not use this file except in compliance with the # https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0. # Elastic License 2.0.
# Custom salt beacon that watches hand-edited directories under # Custom salt beacon that watches the suricata/strelka rule directories for changes
# /opt/so/saltstack/local/salt/ for changes and emits a beacon event per changed # and emits a beacon event per changed directory. This replaces the stock salt
# directory. This replaces the stock salt `inotify` beacon, which leaks a kernel # `inotify` beacon, which leaks a kernel inotify instance every time the minion
# inotify instance every time the minion rebuilds the beacon loader's __context__ # rebuilds the beacon loader's __context__ (orphaning the old pyinotify.Notifier
# (orphaning the old pyinotify.Notifier without closing it) until # without closing it) until fs.inotify.max_user_instances is exhausted and the
# fs.inotify.max_user_instances is exhausted and the beacon dies with EMFILE. # beacon dies with EMFILE. Polling holds zero inotify instances, so the leak is
# Polling holds zero inotify instances, so the leak is impossible, and it keeps # impossible, and it keeps firing during state runs (no blackout).
# firing during state runs (no blackout).
# #
# Detection is poll-based with a per-directory fingerprint persisted to # Detection is poll-based with a per-directory fingerprint persisted to
# WATERMARK_DIR: each pass walks the directory and hashes every file's # WATERMARK_DIR: each pass walks the directory and hashes every file's
@@ -20,10 +19,9 @@
# up on the next one). # up on the next one).
# #
# Each emitted event carries the watched directory path under the configured tag # Each emitted event carries the watched directory path under the configured tag
# (e.g. salt/beacon/<minion>/local_files_beacon/zeek); the push_files reactor # (e.g. salt/beacon/<minion>/rules_beacon/suricata); the push_suricata / push_strelka
# looks the tag up in salt/reactor/pillar_push_map.yaml and writes a push intent, # reactors write a push intent, after which the existing so-push-drainer /
# after which the existing so-push-drainer / orch.push_batch pipeline takes over # orch.push_batch pipeline takes over unchanged.
# unchanged.
import hashlib import hashlib
import logging import logging
@@ -79,9 +77,7 @@ def _fingerprint(directory):
h = hashlib.sha1() h = hashlib.sha1()
if os.path.isdir(directory): if os.path.isdir(directory):
entries = [] entries = []
for root, dirs, files in os.walk(directory): for root, _dirs, files in os.walk(directory):
# zkg packages are git clones; .git churn would fire a state apply on its own.
dirs[:] = [d for d in dirs if d != '.git']
for name in files: for name in files:
full = os.path.join(root, name) full = os.path.join(root, name)
if _excluded(full): if _excluded(full):
@@ -98,22 +94,20 @@ def _fingerprint(directory):
return h.hexdigest() return h.hexdigest()
def _watermark_file(tag, directory): def _watermark_file(tag):
# Keyed by directory: zeek/policy and zeek/zkg share the tag `zeek`. return os.path.join(WATERMARK_DIR, 'rules_beacon_%s.hash' % tag)
scope = hashlib.sha1(directory.encode('utf-8', 'surrogateescape')).hexdigest()[:12]
return os.path.join(WATERMARK_DIR, 'local_files_beacon_%s_%s.hash' % (tag, scope))
def _read_watermark(tag, directory): def _read_watermark(tag):
try: try:
with open(_watermark_file(tag, directory), 'r') as f: with open(_watermark_file(tag), 'r') as f:
return (f.read() or '').strip() or None return (f.read() or '').strip() or None
except IOError: except IOError:
return None return None
def _write_watermark(tag, directory, digest): def _write_watermark(tag, digest):
path = _watermark_file(tag, directory) path = _watermark_file(tag)
try: try:
os.makedirs(WATERMARK_DIR, exist_ok=True) os.makedirs(WATERMARK_DIR, exist_ok=True)
tmp = path + '.tmp' tmp = path + '.tmp'
@@ -121,7 +115,7 @@ def _write_watermark(tag, directory, digest):
f.write(digest) f.write(digest)
os.rename(tmp, path) os.rename(tmp, path)
except OSError: except OSError:
log.exception('local_files_beacon: failed to persist watermark to %s', path) log.exception('rules_beacon: failed to persist watermark to %s', path)
def beacon(config): def beacon(config):
@@ -129,17 +123,17 @@ def beacon(config):
for directory, tag in _paths_from_config(config).items(): for directory, tag in _paths_from_config(config).items():
digest = _fingerprint(directory) digest = _fingerprint(directory)
previous = _read_watermark(tag, directory) previous = _read_watermark(tag)
# First run / missing watermark: seed the digest and emit nothing so a # First run / missing watermark: seed the digest and emit nothing so a
# fresh host does not fire a spurious fleetwide push. # fresh host does not fire a spurious fleetwide push.
if previous is None: if previous is None:
_write_watermark(tag, directory, digest) _write_watermark(tag, digest)
continue continue
if digest != previous: if digest != previous:
_write_watermark(tag, directory, digest) _write_watermark(tag, digest)
retval.append({'tag': tag, 'path': directory}) retval.append({'tag': tag, 'path': directory})
log.info('local_files_beacon: change detected in %s, emitting %s', directory, tag) log.info('rules_beacon: change detected in %s, emitting %s', directory, tag)
return retval return retval
+172
View File
@@ -0,0 +1,172 @@
# 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.
import hashlib
import os
import shutil
import tempfile
import unittest
from unittest.mock import patch
import rules_beacon
class TestRulesBeacon(unittest.TestCase):
def setUp(self):
# Isolate all on-disk state (watermarks and the dirs we fingerprint) in a
# throwaway tree, and point WATERMARK_DIR at it so the real read/write
# helpers run against actual files.
self.tmpdir = tempfile.mkdtemp()
self.state = os.path.join(self.tmpdir, 'state')
patcher = patch.object(rules_beacon, 'WATERMARK_DIR', self.state)
patcher.start()
self.addCleanup(patcher.stop)
def tearDown(self):
shutil.rmtree(self.tmpdir, ignore_errors=True)
def _make_dir(self, name, files=None):
path = os.path.join(self.tmpdir, name)
os.makedirs(path, exist_ok=True)
for fname, content in (files or {}).items():
with open(os.path.join(path, fname), 'w') as f:
f.write(content)
return path
# -- trivial contract -------------------------------------------------
def test_virtual_returns_true(self):
self.assertTrue(rules_beacon.__virtual__())
def test_validate_returns_valid(self):
self.assertEqual(rules_beacon.validate({}), (True, 'valid'))
# -- _paths_from_config -----------------------------------------------
def test_paths_from_config_list_of_dicts(self):
config = [{'interval': 10}, {'paths': {'/a': 'suricata', '/b': 'strelka'}}]
self.assertEqual(
rules_beacon._paths_from_config(config),
{'/a': 'suricata', '/b': 'strelka'},
)
def test_paths_from_config_plain_dict(self):
self.assertEqual(
rules_beacon._paths_from_config({'paths': {'/a': 'suricata'}}),
{'/a': 'suricata'},
)
def test_paths_from_config_skips_non_dict_items(self):
self.assertEqual(rules_beacon._paths_from_config(['bogus', 42]), {})
def test_paths_from_config_paths_not_a_dict(self):
self.assertEqual(rules_beacon._paths_from_config({'paths': 'nope'}), {})
def test_paths_from_config_unexpected_type(self):
self.assertEqual(rules_beacon._paths_from_config('nonsense'), {})
# -- _excluded --------------------------------------------------------
def test_excluded_matches_temp_and_editor_files(self):
for pathname in ('/rules/foo.swp', '/rules/foo~', '/rules/4913', '/rules/.#foo'):
self.assertTrue(rules_beacon._excluded(pathname), pathname)
def test_excluded_allows_real_rule_files(self):
self.assertFalse(rules_beacon._excluded('/rules/suricata.rules'))
# -- _fingerprint -----------------------------------------------------
def test_fingerprint_missing_dir_is_empty_tree_digest(self):
missing = os.path.join(self.tmpdir, 'does-not-exist')
self.assertEqual(rules_beacon._fingerprint(missing), hashlib.sha1().hexdigest())
def test_fingerprint_changes_when_content_changes(self):
d = self._make_dir('rules', {'a.rules': 'alert'})
before = rules_beacon._fingerprint(d)
with open(os.path.join(d, 'a.rules'), 'w') as f:
f.write('alert tcp any any -> any any') # different size
self.assertNotEqual(rules_beacon._fingerprint(d), before)
def test_fingerprint_ignores_excluded_files(self):
d = self._make_dir('rules', {'a.rules': 'alert'})
before = rules_beacon._fingerprint(d)
with open(os.path.join(d, 'a.rules.swp'), 'w') as f:
f.write('editor swap')
self.assertEqual(rules_beacon._fingerprint(d), before)
def test_fingerprint_skips_unstatable_entries(self):
# A dangling symlink appears in os.walk's file list but os.stat raises
# OSError, exercising the except-continue path.
d = self._make_dir('rules', {'a.rules': 'alert'})
good = rules_beacon._fingerprint(d)
os.symlink(os.path.join(d, 'missing-target'), os.path.join(d, 'broken.link'))
self.assertEqual(rules_beacon._fingerprint(d), good)
# -- _read_watermark / _write_watermark -------------------------------
def test_watermark_round_trip(self):
rules_beacon._write_watermark('suricata', 'deadbeef')
self.assertEqual(rules_beacon._read_watermark('suricata'), 'deadbeef')
def test_read_watermark_missing_returns_none(self):
self.assertIsNone(rules_beacon._read_watermark('suricata'))
def test_read_watermark_empty_file_returns_none(self):
os.makedirs(self.state, exist_ok=True)
with open(rules_beacon._watermark_file('suricata'), 'w') as f:
f.write('')
self.assertIsNone(rules_beacon._read_watermark('suricata'))
def test_write_watermark_swallows_oserror(self):
with patch.object(rules_beacon.os, 'makedirs', side_effect=OSError):
rules_beacon._write_watermark('suricata', 'deadbeef')
self.assertIsNone(rules_beacon._read_watermark('suricata'))
# -- beacon -----------------------------------------------------------
def _config(self, mapping):
return [{'paths': mapping}]
def test_beacon_seeds_first_run_and_emits_nothing(self):
with patch.object(rules_beacon, '_fingerprint', return_value='hash1'), \
patch.object(rules_beacon, '_read_watermark', return_value=None), \
patch.object(rules_beacon, '_write_watermark') as mock_write:
result = rules_beacon.beacon(self._config({'/rules/suricata': 'suricata'}))
self.assertEqual(result, [])
mock_write.assert_called_once_with('suricata', 'hash1')
def test_beacon_emits_on_change(self):
with patch.object(rules_beacon, '_fingerprint', return_value='newhash'), \
patch.object(rules_beacon, '_read_watermark', return_value='oldhash'), \
patch.object(rules_beacon, '_write_watermark') as mock_write:
result = rules_beacon.beacon(self._config({'/rules/suricata': 'suricata'}))
self.assertEqual(result, [{'tag': 'suricata', 'path': '/rules/suricata'}])
mock_write.assert_called_once_with('suricata', 'newhash')
def test_beacon_no_change_emits_nothing(self):
with patch.object(rules_beacon, '_fingerprint', return_value='samehash'), \
patch.object(rules_beacon, '_read_watermark', return_value='samehash'), \
patch.object(rules_beacon, '_write_watermark') as mock_write:
result = rules_beacon.beacon(self._config({'/rules/suricata': 'suricata'}))
self.assertEqual(result, [])
mock_write.assert_not_called()
def test_beacon_end_to_end_with_real_files(self):
# Exercise the full stack (real fingerprint + real watermark files) across
# two poll passes: first seeds silently, second fires after a write.
d = self._make_dir('rules', {'a.rules': 'alert'})
config = self._config({d: 'suricata'})
self.assertEqual(rules_beacon.beacon(config), []) # seed pass
self.assertEqual(rules_beacon.beacon(config), []) # unchanged pass
with open(os.path.join(d, 'b.rules'), 'w') as f:
f.write('alert tcp any any -> any any')
self.assertEqual(rules_beacon.beacon(config), [{'tag': 'suricata', 'path': d}])
if __name__ == '__main__':
unittest.main()
+5 -34
View File
@@ -117,25 +117,14 @@ elastic_curl_config:
{% 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: common_sbin:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://common/tools/sbin - source: salt://common/tools/sbin
- user: root - user: 939
- group: root - group: 939
- file_mode: 755 - file_mode: 755
- show_changes: False - show_changes: False
- require:
- file: usr_sbin_perms
{% if GLOBALS.role == 'so-heavynode' %} {% if GLOBALS.role == 'so-heavynode' %}
- exclude_pat: - exclude_pat:
- so-pcap-import - so-pcap-import
@@ -152,26 +141,12 @@ pin_nic_names:
- file: common_sbin - file: common_sbin
- file: statedir - 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: common_sbin_jinja:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://common/tools/sbin_jinja - source: salt://common/tools/sbin_jinja
- user: root - user: 939
- group: root - group: 939
- file_mode: 755 - file_mode: 755
- template: jinja - template: jinja
- show_changes: False - show_changes: False
@@ -184,8 +159,6 @@ so-status_script:
file.managed: file.managed:
- name: /usr/sbin/so-status - name: /usr/sbin/so-status
- source: salt://common/tools/sbin/so-status - source: salt://common/tools/sbin/so-status
- user: root
- group: root
- mode: 755 - mode: 755
{% if GLOBALS.is_sensor %} {% if GLOBALS.is_sensor %}
@@ -217,11 +190,9 @@ sostatus_log:
- replace: False - replace: False
# Install sostatus check cron. This is used to populate Grid. # 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: so-status_check_cron:
cron.present: 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' - name: '/usr/sbin/so-status -j > /opt/so/log/sostatus/status.log 2>&1'
- identifier: so-status_check_cron - identifier: so-status_check_cron
- user: root - user: root
- minute: '*/1' - minute: '*/1'
+14 -42
View File
@@ -18,61 +18,47 @@ copy_so-common_common_tools_sbin:
- name: /opt/so/saltstack/default/salt/common/tools/sbin/so-common - name: /opt/so/saltstack/default/salt/common/tools/sbin/so-common
- source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-common - source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-common
- force: True - force: True
- user: root - preserve: True
- group: root
- mode: 755
copy_so-image-common_common_tools_sbin: copy_so-image-common_common_tools_sbin:
file.copy: file.copy:
- name: /opt/so/saltstack/default/salt/common/tools/sbin/so-image-common - name: /opt/so/saltstack/default/salt/common/tools/sbin/so-image-common
- source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-image-common - source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-image-common
- force: True - force: True
- user: root - preserve: True
- group: root
- mode: 755
copy_soup_manager_tools_sbin: copy_soup_manager_tools_sbin:
file.copy: file.copy:
- name: /opt/so/saltstack/default/salt/manager/tools/sbin/soup - name: /opt/so/saltstack/default/salt/manager/tools/sbin/soup
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/soup - source: {{UPDATE_DIR}}/salt/manager/tools/sbin/soup
- force: True - force: True
- user: root - preserve: True
- group: root
- mode: 755
copy_so-firewall_manager_tools_sbin: copy_so-firewall_manager_tools_sbin:
file.copy: file.copy:
- name: /opt/so/saltstack/default/salt/manager/tools/sbin/so-firewall - name: /opt/so/saltstack/default/salt/manager/tools/sbin/so-firewall
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-firewall - source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-firewall
- force: True - force: True
- user: root - preserve: True
- group: root
- mode: 755
copy_so-yaml_manager_tools_sbin: copy_so-yaml_manager_tools_sbin:
file.copy: file.copy:
- name: /opt/so/saltstack/default/salt/manager/tools/sbin/so-yaml.py - name: /opt/so/saltstack/default/salt/manager/tools/sbin/so-yaml.py
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-yaml.py - source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-yaml.py
- force: True - force: True
- user: root - preserve: True
- group: root
- mode: 755
copy_so-repo-sync_manager_tools_sbin: copy_so-repo-sync_manager_tools_sbin:
file.copy: file.copy:
- name: /opt/so/saltstack/default/salt/manager/tools/sbin/so-repo-sync - name: /opt/so/saltstack/default/salt/manager/tools/sbin/so-repo-sync
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-repo-sync - source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-repo-sync
- user: root - preserve: True
- group: root
- mode: 755
copy_bootstrap-salt_manager_tools_sbin: copy_bootstrap-salt_manager_tools_sbin:
file.copy: file.copy:
- name: /opt/so/saltstack/default/salt/salt/scripts/bootstrap-salt.sh - name: /opt/so/saltstack/default/salt/salt/scripts/bootstrap-salt.sh
- source: {{UPDATE_DIR}}/salt/salt/scripts/bootstrap-salt.sh - source: {{UPDATE_DIR}}/salt/salt/scripts/bootstrap-salt.sh
- user: root - preserve: True
- group: root
- mode: 644
# This section is used to put the new script in place so that it can be called during soup. # This section is used to put the new script in place so that it can be called during soup.
# It is faster than calling the states that normally manage them to put them in place. # It is faster than calling the states that normally manage them to put them in place.
@@ -81,60 +67,46 @@ copy_so-common_sbin:
- name: /usr/sbin/so-common - name: /usr/sbin/so-common
- source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-common - source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-common
- force: True - force: True
- user: root - preserve: True
- group: root
- mode: 755
copy_so-image-common_sbin: copy_so-image-common_sbin:
file.copy: file.copy:
- name: /usr/sbin/so-image-common - name: /usr/sbin/so-image-common
- source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-image-common - source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-image-common
- force: True - force: True
- user: root - preserve: True
- group: root
- mode: 755
copy_soup_sbin: copy_soup_sbin:
file.copy: file.copy:
- name: /usr/sbin/soup - name: /usr/sbin/soup
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/soup - source: {{UPDATE_DIR}}/salt/manager/tools/sbin/soup
- force: True - force: True
- user: root - preserve: True
- group: root
- mode: 755
copy_so-firewall_sbin: copy_so-firewall_sbin:
file.copy: file.copy:
- name: /usr/sbin/so-firewall - name: /usr/sbin/so-firewall
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-firewall - source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-firewall
- force: True - force: True
- user: root - preserve: True
- group: root
- mode: 755
copy_so-yaml_sbin: copy_so-yaml_sbin:
file.copy: file.copy:
- name: /usr/sbin/so-yaml.py - name: /usr/sbin/so-yaml.py
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-yaml.py - source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-yaml.py
- force: True - force: True
- user: root - preserve: True
- group: root
- mode: 755
copy_so-repo-sync_sbin: copy_so-repo-sync_sbin:
file.copy: file.copy:
- name: /usr/sbin/so-repo-sync - name: /usr/sbin/so-repo-sync
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-repo-sync - source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-repo-sync
- force: True - force: True
- user: root - preserve: True
- group: root
- mode: 755
copy_bootstrap-salt_sbin: copy_bootstrap-salt_sbin:
file.copy: file.copy:
- name: /usr/sbin/bootstrap-salt.sh - name: /usr/sbin/bootstrap-salt.sh
- source: {{UPDATE_DIR}}/salt/salt/scripts/bootstrap-salt.sh - source: {{UPDATE_DIR}}/salt/salt/scripts/bootstrap-salt.sh
- force: True - force: True
- user: root - preserve: True
- group: root
- mode: 755
+1 -15
View File
@@ -240,8 +240,7 @@ copy_new_files() {
cd $UPDATE_DIR cd $UPDATE_DIR
rsync -a salt $DEFAULT_SALT_DIR/ --delete "${EXCLUDE_ARGS[@]}" rsync -a salt $DEFAULT_SALT_DIR/ --delete "${EXCLUDE_ARGS[@]}"
rsync -a pillar $DEFAULT_SALT_DIR/ --delete "${EXCLUDE_ARGS[@]}" rsync -a pillar $DEFAULT_SALT_DIR/ --delete "${EXCLUDE_ARGS[@]}"
# Root-executed code; SOC only needs to read it. Local dirs stay socore-owned. chown -R socore:socore $DEFAULT_SALT_DIR/
chown -R root:root $DEFAULT_SALT_DIR/
cd /tmp cd /tmp
} }
@@ -419,19 +418,6 @@ is_single_node_grid() {
grep "role: so-" /etc/salt/grains | grep -E "eval|standalone|import" &> /dev/null grep "role: so-" /etc/salt/grains | grep -E "eval|standalone|import" &> /dev/null
} }
remove_elasticsearch_index_template() {
local template_name=$1
local reason=${2:-"Removing index template for upgrade"}
# check if template exists
if so-elasticsearch-query "_index_template/$template_name" --fail --retry 3 --retry-delay 5 >/dev/null 2>&1; then
echo "Removing Elasticsearch index template: $template_name ($reason)"
if ! so-elasticsearch-query "_index_template/$template_name" -XDELETE --fail --retry 3 --retry-delay 5; then
return 1
fi
fi
}
initialize_elasticsearch_indices() { initialize_elasticsearch_indices() {
local index_names=$1 local index_names=$1
local default_entry=${2:-'{"@timestamp":"0"}'} local default_entry=${2:-'{"@timestamp":"0"}'}
+6 -19
View File
@@ -9,7 +9,6 @@ import sys
import subprocess import subprocess
import os import os
import json import json
import tempfile
sys.path.append('/opt/saltstack/salt/lib/python3.10/site-packages/') sys.path.append('/opt/saltstack/salt/lib/python3.10/site-packages/')
import salt.config import salt.config
@@ -18,21 +17,6 @@ import salt.loader
__opts__ = salt.config.minion_config('/etc/salt/minion') __opts__ = salt.config.minion_config('/etc/salt/minion')
__grains__ = salt.loader.grains(__opts__) __grains__ = salt.loader.grains(__opts__)
def write_atomic(path, value):
# telegraf reads these files on its own schedule; replacing them by rename means it never
# reads a truncated file and reports an empty value as if it were real
directory = os.path.dirname(path)
handle, temp = tempfile.mkstemp(dir=directory)
try:
with os.fdopen(handle, 'w') as f:
f.write(str(value))
os.chmod(temp, 0o644)
os.replace(temp, path)
except Exception:
os.path.exists(temp) and os.unlink(temp)
raise
def check_needs_restarted(): def check_needs_restarted():
osfam = __grains__['os_family'] osfam = __grains__['os_family']
val = '0' val = '0'
@@ -50,7 +34,8 @@ def check_needs_restarted():
else: else:
fail("Unsupported OS") fail("Unsupported OS")
write_atomic(outfile, val) with open(outfile, 'w') as f:
f.write(val)
def check_for_fps(): def check_for_fps():
feat = 'fps' feat = 'fps'
@@ -71,7 +56,8 @@ def check_for_fps():
# Unknown, so assume 0 # Unknown, so assume 0
fps = 0 fps = 0
write_atomic('/opt/so/log/sostatus/fps_enabled', fps) with open('/opt/so/log/sostatus/fps_enabled', 'w') as f:
f.write(str(fps))
def check_for_lks(): def check_for_lks():
feat = 'Lks' feat = 'Lks'
@@ -94,7 +80,8 @@ def check_for_lks():
lks = 1 lks = 1
if lks: if lks:
break break
write_atomic('/opt/so/log/sostatus/lks_enabled', lks) with open('/opt/so/log/sostatus/lks_enabled', 'w') as f:
f.write(str(lks))
def fail(msg): def fail(msg):
print(msg, file=sys.stderr) print(msg, file=sys.stderr)
+7 -81
View File
@@ -5,11 +5,10 @@
# https://securityonion.net/license; you may not use this file except in compliance with the # https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0. # Elastic License 2.0.
# #
# so-kernel-upgrade — install the UEK8 (6.x) kernel, make it the boot default, and once the # so-kernel-upgrade — install the UEK8 (6.x) kernel and make it the boot default.
# node is running it, remove the stock EL9 kernel.
# #
# Security Onion is moving off the EL9 stock kernel (RHCK, 5.14) and UEK7 (5.15) onto UEK8 # Security Onion is moving off the EL9 stock kernel (RHCK, 5.14) and UEK7 (5.15) onto UEK8
# (6.x). Four things have to happen, and the tool has to drive each one: # (6.x). Three things have to happen, and the tool has to drive each one:
# #
# 1. Populate. The manager mirrors the UEK8 packages into /nsm/kernelrepo via so-repo-sync, # 1. Populate. The manager mirrors the UEK8 packages into /nsm/kernelrepo via so-repo-sync,
# and serves them to the grid over https://<manager>/kernelrepo. Until that sync runs the # and serves them to the grid over https://<manager>/kernelrepo. Until that sync runs the
@@ -27,21 +26,10 @@
# - From the stock EL9 kernel (RHCK, 5.14, no UEK) it is a flavor CROSS that is NOT # - From the stock EL9 kernel (RHCK, 5.14, no UEK) it is a flavor CROSS that is NOT
# auto-promoted, so the box keeps booting RHCK until grubby is told otherwise. # auto-promoted, so the box keeps booting RHCK until grubby is told otherwise.
# This tool inspects the running kernel and only runs 'grubby --set-default' for RHCK. # This tool inspects the running kernel and only runs 'grubby --set-default' for RHCK.
# 4. Clean up. Once the node is actually RUNNING UEK8 the stock kernel packages are dead
# weight -- disk in /boot and a stale GRUB entry. They cannot come off any earlier:
# dnf's protect_running_kernel refuses to erase the booted kernel-core, so the removal
# has to wait for the reboot. Waiting is also the safer sequencing on its own terms --
# the node has proven it comes up on UEK8 before its fallback is deleted. That is why
# the removal does not happen in the uek7 branch either, where dnf would allow it.
# #
# Every one of those failure modes is silent by default. This tool handles each case and fails # Every one of those failure modes is silent by default. This tool handles each case and fails
# loudly when it cannot, rather than reporting success while changing nothing. # loudly when it cannot, rather than reporting success while changing nothing.
# #
# Invocation: with no arguments it drives the whole sequence for whatever kernel the node is
# on. With --cleanup it does the step 4 removal ONLY, and no-ops on a node that isn't running
# UEK8 yet -- that is the form the common highstate calls (remove_stock_kernel in
# salt/common/init.sls) so the cleanup lands grid-wide after each node reboots.
#
# Manager vs minion: only the manager owns /nsm/kernelrepo, so only the manager can populate # Manager vs minion: only the manager owns /nsm/kernelrepo, so only the manager can populate
# it. If the repo is empty here, a manager runs so-repo-sync itself; a minion has no way to # it. If the repo is empty here, a manager runs so-repo-sync itself; a minion has no way to
# fix it and exits non-zero telling the admin to sync the manager first. # fix it and exits non-zero telling the admin to sync the manager first.
@@ -61,11 +49,6 @@ KERNEL_REPO_DIR="/nsm/kernelrepo"
REPOSYNC_CONF="/opt/so/conf/reposync/repodownload.conf" REPOSYNC_CONF="/opt/so/conf/reposync/repodownload.conf"
GLOBAL_PILLAR="/opt/so/saltstack/local/pillar/global/soc_global.sls" GLOBAL_PILLAR="/opt/so/saltstack/local/pillar/global/soc_global.sls"
# Stock EL9 (RHCK) kernel packages, removed only once the node is running UEK8 (see step 4
# in the header). Left deliberately narrow: UEK7 kernel-uek builds age out on their own via
# installonly_limit=3, and kernel-devel/kernel-headers are not touched.
RHCK_PKGS="kernel kernel-core kernel-modules kernel-modules-core kernel-tools kernel-tools-libs"
log() { echo "[so-kernel-upgrade] $*"; } log() { echo "[so-kernel-upgrade] $*"; }
die() { echo "[so-kernel-upgrade] ERROR: $*" >&2; exit 1; } die() { echo "[so-kernel-upgrade] ERROR: $*" >&2; exit 1; }
@@ -166,13 +149,8 @@ ensure_kernel_repo() {
} }
reboot_notice() { reboot_notice() {
[ "$(uname -r)" = "$(basename "$1" | sed 's/^vmlinuz-//')" ] && return 0 [ "$(uname -r)" = "$(basename "$1" | sed 's/^vmlinuz-//')" ] \
log "REBOOT REQUIRED to start using the UEK8 kernel (currently running $(uname -r))." || log "REBOOT REQUIRED to start using the UEK8 kernel (currently running $(uname -r))."
# The stock kernel can't be removed until it stops being the running one, so say when
# that will happen rather than leaving the admin to wonder if it was missed.
[ -n "$(rhck_installed)" ] \
&& log "The stock EL9 kernel is left in place until then; it is removed by the next highstate after the reboot."
return 0
} }
# Keep future kernel updates on the UEK line rather than falling back to RHCK. Oracle ships # Keep future kernel updates on the UEK line rather than falling back to RHCK. Oracle ships
@@ -184,32 +162,6 @@ set_default_kernel_conf() {
fi fi
} }
# Which of RHCK_PKGS are actually installed, one per line. rpm -qa treats each argument as a
# name glob and prints only what it finds, so a package that was never installed (or is
# already gone) simply doesn't appear -- no "not installed" noise and no non-zero exit.
rhck_installed() {
rpm -qa $RHCK_PKGS 2>/dev/null
}
# Remove the stock EL9 kernel. Only ever called once the running kernel is UEK8. The rpm
# check above is the idempotency guard, so this is a cheap no-op on every highstate after
# the first one -- it costs an rpm query, not a dnf transaction.
remove_rhck() {
local installed; installed="$(rhck_installed)"
if [ -z "$installed" ]; then
log "no stock EL9 (RHCK) kernel packages installed; nothing to remove."
return 0
fi
log "running UEK8; removing the stock EL9 (RHCK) kernel packages:"
echo "$installed" | sed 's/^/[so-kernel-upgrade] /'
dnf -y remove $RHCK_PKGS || die "failed to remove the stock EL9 kernel packages"
installed="$(rhck_installed)"
[ -z "$installed" ] || die "dnf reported success but these remain: $(echo $installed)"
log "stock EL9 kernel packages removed."
}
# Make sure a UEK8 kernel is installed, leaving its boot entry in INSTALLED_UEK8. If one is # Make sure a UEK8 kernel is installed, leaving its boot entry in INSTALLED_UEK8. If one is
# already present we leave the repo alone -- it may be disabled or empty and we don't need it # already present we leave the repo alone -- it may be disabled or empty and we don't need it
# just to flip the boot default. Otherwise install the explicit NEVRA, not the bare package # just to flip the boot default. Otherwise install the explicit NEVRA, not the bare package
@@ -232,38 +184,12 @@ ensure_uek8_installed() {
log "installed UEK8 kernel: $INSTALLED_UEK8" log "installed UEK8 kernel: $INSTALLED_UEK8"
} }
# --cleanup does step 4 and nothing else. It exits 0 rather than failing on a node that
# isn't on UEK8 yet: the highstate gates on 'uname -r' before calling this, and a state that
# fails whenever that gate races would be worse than one that says what it's waiting for.
case "$1" in
"")
;;
--cleanup)
if [ "$(running_flavor)" != uek8 ]; then
log "not running a UEK8 kernel yet (currently $(uname -r)); leaving the stock EL9 kernel in place."
log "Run so-kernel-upgrade with no arguments to install UEK8, then reboot."
exit 0
fi
set_default_kernel_conf
remove_rhck
exit 0
;;
*)
echo "Usage: so-kernel-upgrade [--cleanup]" >&2
echo " (no arguments) install UEK8, make it the boot default, clean up once it's running" >&2
echo " --cleanup remove the stock EL9 kernel; no-op unless already running UEK8" >&2
exit 1
;;
esac
case "$(running_flavor)" in case "$(running_flavor)" in
uek8) uek8)
# Already on the 6.x UEK line. A plain 'dnf update' keeps this node current within the # Already on the 6.x UEK line. A plain 'dnf update' keeps this node current within the
# lineage and auto-promotes newer builds, so there is no install or grubby work left -- # lineage and auto-promotes newer builds, so there is nothing for this tool to do.
# only the step 4 cleanup, which this is the first point in the sequence that can run it. log "already running a UEK8 kernel ($(uname -r)); nothing to do."
log "already running a UEK8 kernel ($(uname -r)); no kernel install needed." exit 0
set_default_kernel_conf
remove_rhck
;; ;;
uek7) uek7)
+2 -12
View File
@@ -134,7 +134,6 @@ if [[ $EXCLUDE_STARTUP_ERRORS == 'Y' ]]; then
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Redis may have been restarted" # Redis likely restarted by salt EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Redis may have been restarted" # Redis likely restarted by salt
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|file already closed" # Go logging race condition during container restart EXCLUDED_ERRORS="$EXCLUDED_ERRORS|file already closed" # Go logging race condition during container restart
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|relation \"audit_settings\" does not exist" # salt checking for changes before SOC starts EXCLUDED_ERRORS="$EXCLUDED_ERRORS|relation \"audit_settings\" does not exist" # salt checking for changes before SOC starts
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Error in plugin: elasticsearch: Unable to retrieve master node information" # expected error while ES is upgrading/electing a master
fi fi
if [[ $EXCLUDE_FALSE_POSITIVE_ERRORS == 'Y' ]]; then if [[ $EXCLUDE_FALSE_POSITIVE_ERRORS == 'Y' ]]; then
@@ -155,8 +154,6 @@ if [[ $EXCLUDE_FALSE_POSITIVE_ERRORS == 'Y' ]]; then
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|id.orig_h" # false positive (zeek test data) EXCLUDED_ERRORS="$EXCLUDED_ERRORS|id.orig_h" # false positive (zeek test data)
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|emerging-all.rules" # false positive (error in rulename) EXCLUDED_ERRORS="$EXCLUDED_ERRORS|emerging-all.rules" # false positive (error in rulename)
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|invalid query input" # false positive (Invalid user input in hunt query) EXCLUDED_ERRORS="$EXCLUDED_ERRORS|invalid query input" # false positive (Invalid user input in hunt query)
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|no data available for the requested dates" # false positive (pcap cypress test submits a job with an empty time frame)
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|no job processor" # false positive (same empty-time-frame job on import nodes, where no pcap processor runs)
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|example" # false positive (example test data) EXCLUDED_ERRORS="$EXCLUDED_ERRORS|example" # false positive (example test data)
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|status 200" # false positive (request successful, contained error string in content) EXCLUDED_ERRORS="$EXCLUDED_ERRORS|status 200" # false positive (request successful, contained error string in content)
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|app_layer.error" # false positive (suricata 7) in stats.log e.g. app_layer.error.imap.parser | Total | 0 EXCLUDED_ERRORS="$EXCLUDED_ERRORS|app_layer.error" # false positive (suricata 7) in stats.log e.g. app_layer.error.imap.parser | Total | 0
@@ -172,12 +169,6 @@ if [[ $EXCLUDE_FALSE_POSITIVE_ERRORS == 'Y' ]]; then
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Error while parsing document for index \[.ds-logs-kratos-so-.*object mapping for \[file\]" # false positive (mapping error occuring BEFORE kratos index has rolled over in 2.4.210) EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Error while parsing document for index \[.ds-logs-kratos-so-.*object mapping for \[file\]" # false positive (mapping error occuring BEFORE kratos index has rolled over in 2.4.210)
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|No such container" # false positive (telegraf trying to run stats on an old container) EXCLUDED_ERRORS="$EXCLUDED_ERRORS|No such container" # false positive (telegraf trying to run stats on an old container)
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|passwords do not match" # false positive (automated hydra test) EXCLUDED_ERRORS="$EXCLUDED_ERRORS|passwords do not match" # false positive (automated hydra test)
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Request did not pass preprocessing" # expected WARN log lines indicating invalid auth header
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Missing or invalid authorization header for bearer token" # expected WARN log lines indicating invalid auth header
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Unexpected authorization header" # expected WARN log lines indicating invalid auth header
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Missing ory_kratos_session cookie" # expected WARN log lines indicating invalid auth header
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Static assets preprocessor only supports GET and HEAD requests" # expected WARN log lines indicating invalid auth header
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|respondError" # respondError is a function name, output via http middleware as standard request logging
fi fi
if [[ $EXCLUDE_KNOWN_ERRORS == 'Y' ]]; then if [[ $EXCLUDE_KNOWN_ERRORS == 'Y' ]]; then
@@ -240,9 +231,8 @@ if [[ $EXCLUDE_KNOWN_ERRORS == 'Y' ]]; then
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|from NIC checksum offloading" # zeek reporter.log EXCLUDED_ERRORS="$EXCLUDED_ERRORS|from NIC checksum offloading" # zeek reporter.log
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|marked for removal" # docker container getting recycled EXCLUDED_ERRORS="$EXCLUDED_ERRORS|marked for removal" # docker container getting recycled
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|tcp 127.0.0.1:6791: bind: address already in use" # so-elastic-fleet agent restarting. Seen starting w/ 8.18.8 https://github.com/elastic/kibana/issues/201459 EXCLUDED_ERRORS="$EXCLUDED_ERRORS|tcp 127.0.0.1:6791: bind: address already in use" # so-elastic-fleet agent restarting. Seen starting w/ 8.18.8 https://github.com/elastic/kibana/issues/201459
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|TransformTask\] \[logs-.*user so_kibana lacks the required permissions" # Known issue with integrations starting transform jobs that are explicitly not allowed to start as a system user EXCLUDED_ERRORS="$EXCLUDED_ERRORS|TransformTask\] \[logs-(tychon|aws_billing|microsoft_defender_endpoint|armis|o365_metrics|microsoft_sentinel|snyk|cyera|island_browser).*user so_kibana lacks the required permissions \[(logs|metrics)-\1" # Known issue with integrations starting transform jobs that are explicitly not allowed to start as a system user. This error should not be seen on fresh ES 9.3.3 installs or after SO 3.1.0 with soups addition of check_transform_health_and_reauthorize()
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|manifest unknown" # appears in so-dockerregistry log for so-tcpreplay following docker upgrade to 29.2.1-1 EXCLUDED_ERRORS="$EXCLUDED_ERRORS|manifest unknown" # appears in so-dockerregistry log for so-tcpreplay following docker upgrade to 29.2.1-1
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Could not index event to Elasticsearch.*\"version\" => \"9.0.8\"" # Expected during Elastic upgrade temporarily, as policies referencing older pipelines are updated
fi fi
RESULT=0 RESULT=0
@@ -307,4 +297,4 @@ else
echo -e "\nResult: One or more errors found" echo -e "\nResult: One or more errors found"
fi fi
exit $RESULT exit $RESULT
+3
View File
@@ -35,6 +35,9 @@ case $1 in
"elastic-fleet"|"elasticfleet") "elastic-fleet"|"elasticfleet")
docker_check_running "elastic-fleet" "--stop" docker_check_running "elastic-fleet" "--stop"
docker rm "so-elastic-fleet" 2> /dev/null docker rm "so-elastic-fleet" 2> /dev/null
# Removing the elastic fleet state directory, so that the next startup re-enrolls with a fresh policy
rm -rf /opt/so/conf/elastic-fleet/state
salt-call state.apply elasticfleet queue=True salt-call state.apply elasticfleet queue=True
;; ;;
*) *)
+47 -78
View File
@@ -8,37 +8,21 @@
# Elastic License 2.0. # Elastic License 2.0.
SENSOR_DIR="${SENSOR_DIR:-/nsm}" SENSOR_DIR='/nsm'
CRIT_DISK_USAGE=90 CRIT_DISK_USAGE=90
LOG="${LOG:-/opt/so/log/sensor_clean.log}" CUR_USAGE=$(df -P $SENSOR_DIR | tail -1 | awk '{print $5}' | tr -d %)
LOCK="${LOCK:-/var/tmp/so-sensor-clean.lock}" LOG="/opt/so/log/sensor_clean.log"
MAX_PASSES=100 TODAY=$(date -u "+%Y-%m-%d")
ZEEK_LOGS="$SENSOR_DIR/zeek/logs"
STRELKA_FILES="$SENSOR_DIR/strelka/processed"
SURICATA_LOGS="$SENSOR_DIR/suricata"
PCAPS="$SENSOR_DIR/pcapout"
log() {
echo "$(date) - $*" >>"$LOG"
}
disk_usage() {
df -P "$SENSOR_DIR" | tail -1 | awk '{print $5}' | tr -d %
}
disk_avail() {
df -P "$SENSOR_DIR" | tail -1 | awk '{print $4}'
}
# sets REMOVED=1 if anything was actually deleted
clean() { clean() {
## find the oldest Zeek logs directory ## find the oldest Zeek logs directory
OLDEST_DIR=$(ls "$ZEEK_LOGS" 2>/dev/null | grep -v "current" | grep -v "stats" | grep -v "packetloss" | grep -v "zeek_clean" | sort | head -n 1) OLDEST_DIR=$(ls /nsm/zeek/logs/ | grep -v "current" | grep -v "stats" | grep -v "packetloss" | grep -v "zeek_clean" | sort | head -n 1)
if [ -n "$OLDEST_DIR" ]; then if [ -z "$OLDEST_DIR" -o "$OLDEST_DIR" == ".." -o "$OLDEST_DIR" == "." ]; then
log "Removing directory: $ZEEK_LOGS/$OLDEST_DIR" echo "$(date) - No old Zeek logs available to clean up in /nsm/zeek/logs/" >>$LOG
rm -rf "$ZEEK_LOGS/$OLDEST_DIR" #exit 0
REMOVED=1 else
echo "$(date) - Removing directory: /nsm/zeek/logs/$OLDEST_DIR" >>$LOG
rm -rf /nsm/zeek/logs/"$OLDEST_DIR"
fi fi
## Remarking for now, as we are moving extracted files to /nsm/strelka/processed ## Remarking for now, as we are moving extracted files to /nsm/strelka/processed
@@ -59,73 +43,58 @@ clean() {
#fi #fi
## Clean up Zeek extracted files processed by Strelka ## Clean up Zeek extracted files processed by Strelka
OLDEST_STRELKA=$(find "$STRELKA_FILES" -type f -printf '%T+ %p\n' 2>/dev/null | sort -n | head -n 1) STRELKA_FILES='/nsm/strelka/processed'
if [ -n "$OLDEST_STRELKA" ]; then OLDEST_STRELKA=$(find $STRELKA_FILES -type f -printf '%T+ %p\n' | sort -n | head -n 1)
if [ -z "$OLDEST_STRELKA" -o "$OLDEST_STRELKA" == ".." -o "$OLDEST_STRELKA" == "." ]; then
echo "$(date) - No old files available to clean up in $STRELKA_FILES" >>$LOG
else
OLDEST_STRELKA_DATE=$(echo $OLDEST_STRELKA | awk '{print $1}' | cut -d+ -f1) OLDEST_STRELKA_DATE=$(echo $OLDEST_STRELKA | awk '{print $1}' | cut -d+ -f1)
log "Removing extracted files for $OLDEST_STRELKA_DATE" OLDEST_STRELKA_FILE=$(echo $OLDEST_STRELKA | awk '{print $2}')
REMOVED=1 echo "$(date) - Removing extracted files for $OLDEST_STRELKA_DATE" >>$LOG
find "$STRELKA_FILES" -type f -printf '%T+ %p\n' 2>/dev/null | grep $OLDEST_STRELKA_DATE | awk '{print $2}' | while read FILE; do find $STRELKA_FILES -type f -printf '%T+ %p\n' | grep $OLDEST_STRELKA_DATE | awk '{print $2}' | while read FILE; do
log "Removing file: $FILE" echo "$(date) - Removing file: $FILE" >>$LOG
rm -f "$FILE" rm -f "$FILE"
done done
fi fi
## Clean up Suricata log files ## Clean up Suricata log files
OLDEST_SURICATA=$(find "$SURICATA_LOGS" -type f -printf '%T+ %p\n' 2>/dev/null | sort -n | head -n 1) SURICATA_LOGS='/nsm/suricata'
if [ -n "$OLDEST_SURICATA" ]; then OLDEST_SURICATA=$(find $SURICATA_LOGS -type f -printf '%T+ %p\n' | sort -n | head -n 1)
if [[ -z "$OLDEST_SURICATA" ]] || [[ "$OLDEST_SURICATA" == ".." ]] || [[ "$OLDEST_SURICATA" == "." ]]; then
echo "$(date) - No old files available to clean up in $SURICATA_LOGS" >>$LOG
else
OLDEST_SURICATA_DATE=$(echo $OLDEST_SURICATA | awk '{print $1}' | cut -d+ -f1) OLDEST_SURICATA_DATE=$(echo $OLDEST_SURICATA | awk '{print $1}' | cut -d+ -f1)
log "Removing logs for $OLDEST_SURICATA_DATE" OLDEST_SURICATA_FILE=$(echo $OLDEST_SURICATA | awk '{print $2}')
REMOVED=1 echo "$(date) - Removing logs for $OLDEST_SURICATA_DATE" >>$LOG
find "$SURICATA_LOGS" -type f -printf '%T+ %p\n' 2>/dev/null | grep $OLDEST_SURICATA_DATE | awk '{print $2}' | while read FILE; do find $SURICATA_LOGS -type f -printf '%T+ %p\n' | grep $OLDEST_SURICATA_DATE | awk '{print $2}' | while read FILE; do
log "Removing file: $FILE" echo "$(date) - Removing file: $FILE" >>$LOG
rm -f "$FILE" rm -f "$FILE"
done done
fi fi
## Clean up extracted pcaps ## Clean up extracted pcaps
OLDEST_PCAP=$(find "$PCAPS" -type f -printf '%T+ %p\n' 2>/dev/null | sort -n | head -n 1) PCAPS='/nsm/pcapout'
if [ -n "$OLDEST_PCAP" ]; then OLDEST_PCAP=$(find $PCAPS -type f -printf '%T+ %p\n' | sort -n | head -n 1)
if [ -z "$OLDEST_PCAP" -o "$OLDEST_PCAP" == ".." -o "$OLDEST_PCAP" == "." ]; then
echo "$(date) - No old files available to clean up in $PCAPS" >>$LOG
else
OLDEST_PCAP_DATE=$(echo $OLDEST_PCAP | awk '{print $1}' | cut -d+ -f1) OLDEST_PCAP_DATE=$(echo $OLDEST_PCAP | awk '{print $1}' | cut -d+ -f1)
log "Removing extracted files for $OLDEST_PCAP_DATE" OLDEST_PCAP_FILE=$(echo $OLDEST_PCAP | awk '{print $2}')
REMOVED=1 echo "$(date) - Removing extracted files for $OLDEST_PCAP_DATE" >>$LOG
find "$PCAPS" -type f -printf '%T+ %p\n' 2>/dev/null | grep $OLDEST_PCAP_DATE | awk '{print $2}' | while read FILE; do find $PCAPS -type f -printf '%T+ %p\n' | grep $OLDEST_PCAP_DATE | awk '{print $2}' | while read FILE; do
log "Removing file: $FILE" echo "$(date) - Removing file: $FILE" >>$LOG
rm -f "$FILE" rm -f "$FILE"
done done
fi fi
} }
# Only one instance at a time; the lock is the fd, so it releases on any exit # Check to see if we are already running
exec 9>"$LOCK" || exit 1 NUM_RUNNING=$(pgrep -cf "/bin/bash /usr/sbin/so-sensor-clean")
if ! flock -n 9; then [ "$NUM_RUNNING" -gt 1 ] && echo "$(date) - $NUM_RUNNING sensor clean script processes running...exiting." >>$LOG && exit 0
log "another so-sensor-clean is already running (lock $LOCK held); exiting"
exit 0 if [ "$CUR_USAGE" -gt "$CRIT_DISK_USAGE" ]; then
while [ "$CUR_USAGE" -gt "$CRIT_DISK_USAGE" ]; do
clean
CUR_USAGE=$(df -P $SENSOR_DIR | tail -1 | awk '{print $5}' | tr -d %)
done
fi fi
CUR_USAGE=$(disk_usage)
[ "$CUR_USAGE" -gt "$CRIT_DISK_USAGE" ] || exit 0
log "$SENSOR_DIR at ${CUR_USAGE}% (threshold ${CRIT_DISK_USAGE}%); starting cleanup"
PASS=0
while [ "$CUR_USAGE" -gt "$CRIT_DISK_USAGE" ]; do
PASS=$((PASS + 1))
if [ "$PASS" -gt "$MAX_PASSES" ]; then
log "stopping after $MAX_PASSES passes; $SENSOR_DIR still at ${CUR_USAGE}%"
break
fi
REMOVED=0
BEFORE=$(disk_avail)
clean
CUR_USAGE=$(disk_usage)
if [ "$REMOVED" -eq 0 ]; then
log "nothing left to remove in $ZEEK_LOGS, $STRELKA_FILES, $SURICATA_LOGS, $PCAPS; $SENSOR_DIR still at ${CUR_USAGE}% - space is consumed outside of NSM cleanup scope"
break
fi
if [ "$(disk_avail)" -le "$BEFORE" ]; then
log "pass $PASS freed no space; $SENSOR_DIR still at ${CUR_USAGE}% - stopping until next run"
break
fi
done
+2
View File
@@ -29,6 +29,8 @@ case $1 in
"elasticfleet"|"elastic-fleet") "elasticfleet"|"elastic-fleet")
docker_check_running "elastic-fleet" "--stop" docker_check_running "elastic-fleet" "--stop"
docker rm "so-elastic-fleet" 2> /dev/null docker rm "so-elastic-fleet" 2> /dev/null
# Removing the elastic fleet state directory, so that the next startup re-enrolls with a fresh policy
rm -rf /opt/so/conf/elastic-fleet/state
;; ;;
*) *)
docker_check_running "$1" "--stop" docker_check_running "$1" "--stop"
+1 -3
View File
@@ -125,6 +125,4 @@ else
RAIDSTATUS=1 RAIDSTATUS=1
fi fi
# telegraf reads this file; write aside and rename so it never sees a half-written file echo "nsmraid=$RAIDSTATUS" > /opt/so/log/raid/status.log
echo "nsmraid=$RAIDSTATUS" > /opt/so/log/raid/status.log.tmp
mv -f /opt/so/log/raid/status.log.tmp /opt/so/log/raid/status.log
@@ -25,7 +25,7 @@ LAST_HIGHSTATE_END=$([ -e "/opt/so/log/salt/lasthighstate" ] && date -r /opt/so/
LAST_HEALTHCHECK_STATE_APPLY=$([ -e "/opt/so/log/salt/state-apply-test" ] && date -r /opt/so/log/salt/state-apply-test +%s || echo 0) LAST_HEALTHCHECK_STATE_APPLY=$([ -e "/opt/so/log/salt/state-apply-test" ] && date -r /opt/so/log/salt/state-apply-test +%s || echo 0)
# SETTING THRESHOLD TO ANYTHING UNDER 600 seconds may cause a lot of salt-minion restarts since the job to touch the file occurs every 5-8 minutes by default # SETTING THRESHOLD TO ANYTHING UNDER 600 seconds may cause a lot of salt-minion restarts since the job to touch the file occurs every 5-8 minutes by default
# THRESHOLD is derived from the salt schedule highstate interval + 1 hour, so the minion-check grace period tracks the schedule automatically. # THRESHOLD is derived from the salt schedule highstate interval + 1 hour, so the minion-check grace period tracks the schedule automatically.
THRESHOLD=$(( ({{ SCHEDULEMERGED.highstate_interval_minutes }} + 60) * 60 )) #within how many seconds the file /opt/so/log/salt/state-apply-test must have been touched/modified before the salt minion is restarted THRESHOLD=$(( ({{ SCHEDULEMERGED.highstate_interval_hours }} + 1) * 3600 )) #within how many seconds the file /opt/so/log/salt/state-apply-test must have been touched/modified before the salt minion is restarted
THRESHOLD_DATE=$((LAST_HEALTHCHECK_STATE_APPLY+THRESHOLD)) THRESHOLD_DATE=$((LAST_HEALTHCHECK_STATE_APPLY+THRESHOLD))
logCmd() { logCmd() {
+2 -9
View File
@@ -1,12 +1,6 @@
docker: docker:
range: '172.17.1.0/24' range: '172.17.1.0/24'
gateway: '172.17.1.1' gateway: '172.17.1.1'
networks:
sobridge: {}
soauth:
range: '172.17.2.0/24'
gateway: '172.17.2.1'
manager_only: True
ulimits: ulimits:
- name: nofile - name: nofile
soft: 1048576 soft: 1048576
@@ -64,18 +58,18 @@ docker:
ulimits: [] ulimits: []
'so-kratos': 'so-kratos':
final_octet: 28 final_octet: 28
networks: ['soauth']
port_bindings: port_bindings:
- 0.0.0.0:4433:4433 - 0.0.0.0:4433:4433
- 0.0.0.0:4434:4434
custom_bind_mounts: [] custom_bind_mounts: []
extra_hosts: [] extra_hosts: []
extra_env: [] extra_env: []
ulimits: [] ulimits: []
'so-hydra': 'so-hydra':
final_octet: 30 final_octet: 30
networks: ['soauth']
port_bindings: port_bindings:
- 0.0.0.0:4444:4444 - 0.0.0.0:4444:4444
- 0.0.0.0:4445:4445
custom_bind_mounts: [] custom_bind_mounts: []
extra_hosts: [] extra_hosts: []
extra_env: [] extra_env: []
@@ -134,7 +128,6 @@ docker:
ulimits: [] ulimits: []
'so-soc': 'so-soc':
final_octet: 34 final_octet: 34
networks: ['sobridge', 'soauth']
port_bindings: port_bindings:
- 0.0.0.0:9822:9822 - 0.0.0.0:9822:9822
custom_bind_mounts: [] custom_bind_mounts: []
+3 -21
View File
@@ -1,26 +1,8 @@
{% import_yaml 'docker/defaults.yaml' as DOCKERDEFAULTS %} {% import_yaml 'docker/defaults.yaml' as DOCKERDEFAULTS %}
{% set DOCKERMERGED = salt['pillar.get']('docker', DOCKERDEFAULTS.docker, merge=True) %} {% set DOCKERMERGED = salt['pillar.get']('docker', DOCKERDEFAULTS.docker, merge=True) %}
{% set RANGESPLIT = DOCKERMERGED.range.split('.') %}
{% if DOCKERMERGED.networks.sobridge is not mapping %} {% set FIRSTTHREE = RANGESPLIT[0] ~ '.' ~ RANGESPLIT[1] ~ '.' ~ RANGESPLIT[2] ~ '.' %}
{% do DOCKERMERGED.networks.update({'sobridge': {}}) %}
{% endif %}
{% do DOCKERMERGED.networks['sobridge'].update({'range': DOCKERMERGED.range, 'gateway': DOCKERMERGED.gateway}) %}
{% for netname, net in DOCKERMERGED.networks.items() %}
{% set RANGESPLIT = net.range.split('.') %}
{% do net.update({'prefix': RANGESPLIT[0] ~ '.' ~ RANGESPLIT[1] ~ '.' ~ RANGESPLIT[2] ~ '.'}) %}
{% endfor %}
{% for container, vals in DOCKERMERGED.containers.items() %} {% for container, vals in DOCKERMERGED.containers.items() %}
{% set CONTAINER_NETS = vals.get('networks', ['sobridge']) %} {% do DOCKERMERGED.containers[container].update({'ip': FIRSTTHREE ~ DOCKERMERGED.containers[container].final_octet}) %}
{% set IPS = {} %}
{% for netname in CONTAINER_NETS %}
{% do IPS.update({netname: DOCKERMERGED.networks[netname].prefix ~ vals.final_octet}) %}
{% endfor %}
{% do DOCKERMERGED.containers[container].update({
'networks': CONTAINER_NETS,
'ips': IPS,
'network': CONTAINER_NETS[0],
'ip': IPS[CONTAINER_NETS[0]]
}) %}
{% endfor %} {% endfor %}
+6 -10
View File
@@ -71,19 +71,15 @@ dockerreserveports:
- source: salt://common/files/99-reserved-ports.conf - source: salt://common/files/99-reserved-ports.conf
- name: /etc/sysctl.d/99-reserved-ports.conf - name: /etc/sysctl.d/99-reserved-ports.conf
{% for NETNAME, NETWORK in DOCKERMERGED.networks.items() %} sos_docker_net:
{% if not NETWORK.get('manager_only') or GLOBALS.get('is_manager', False) %}
sos_docker_net_{{ NETNAME }}:
docker_network.present: docker_network.present:
- name: {{ NETNAME }} - name: sobridge
- subnet: {{ NETWORK.range }} - subnet: {{ DOCKERMERGED.range }}
- gateway: {{ NETWORK.gateway }} - gateway: {{ DOCKERMERGED.gateway }}
- options: - options:
com.docker.network.bridge.name: '{{ NETNAME }}' com.docker.network.bridge.name: 'sobridge'
com.docker.network.driver.mtu: '1500' com.docker.network.driver.mtu: '1500'
com.docker.network.bridge.enable_ip_masquerade: 'true' com.docker.network.bridge.enable_ip_masquerade: 'true'
com.docker.network.bridge.enable_icc: 'true' com.docker.network.bridge.enable_icc: 'true'
com.docker.network.bridge.host_binding_ipv4: '0.0.0.0' com.docker.network.bridge.host_binding_ipv4: '0.0.0.0'
- unless: ip l | grep {{ NETNAME }} - unless: ip l | grep sobridge
{% endif %}
{% endfor %}
-44
View File
@@ -7,40 +7,6 @@ docker:
description: Default docker IP range for containers. description: Default docker IP range for containers.
helpLink: docker helpLink: docker
advanced: True advanced: True
networks:
sobridge:
description: |
The default docker network, carrying most containers. Its range and gateway are taken
from the docker.range and docker.gateway settings above rather than set here.
helpLink: docker
readonly: True
advanced: True
global: True
soauth:
range:
description: |
IP range for the soauth docker network, an isolated network for the authentication
services, so that the Kratos and Hydra admin APIs are only reachable from the
containers placed on it.
helpLink: docker
readonly: True
advanced: True
global: True
gateway:
description: Gateway for the soauth docker network.
helpLink: docker
readonly: True
advanced: True
global: True
manager_only:
description: |
Limits the soauth network to grid members running the authentication containers,
instead of creating it on every node.
helpLink: docker
readonly: True
advanced: True
global: True
forcedType: bool
ulimits: ulimits:
description: | description: |
Default ulimit settings applied to all containers via the Docker daemon. Each entry specifies a resource name (e.g. nofile, memlock, core, nproc) with soft and hard limits. Individual container ulimits override these defaults. Valid resource names include: cpu, fsize, data, stack, core, rss, nproc, nofile, memlock, as, locks, sigpending, msgqueue, nice, rtprio, rttime. Default ulimit settings applied to all containers via the Docker daemon. Each entry specifies a resource name (e.g. nofile, memlock, core, nproc) with soft and hard limits. Individual container ulimits override these defaults. Valid resource names include: cpu, fsize, data, stack, core, rss, nproc, nofile, memlock, as, locks, sigpending, msgqueue, nice, rtprio, rttime.
@@ -68,16 +34,6 @@ docker:
readonly: True readonly: True
advanced: True advanced: True
global: True global: True
networks:
description: |
Docker networks this container is attached to. The first entry is the container's
primary network and determines the address its published ports are forwarded to.
Defaults to sobridge when unset.
helpLink: docker
readonly: True
advanced: True
global: True
forcedType: "[]string"
port_bindings: port_bindings:
description: List of port bindings for the container. description: List of port bindings for the container.
helpLink: docker helpLink: docker
+2 -3
View File
@@ -21,7 +21,6 @@ elastalert:
- gid: 933 - gid: 933
- home: /opt/so/conf/elastalert - home: /opt/so/conf/elastalert
- createhome: False - createhome: False
- shell: /sbin/nologin
elastalogdir: elastalogdir:
file.directory: file.directory:
@@ -34,8 +33,8 @@ elastalert_sbin:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://elastalert/tools/sbin - source: salt://elastalert/tools/sbin
- user: root - user: 933
- group: root - group: 939
- file_mode: 755 - file_mode: 755
#elastalert_sbin_jinja: #elastalert_sbin_jinja:
@@ -19,7 +19,6 @@ elastic-agent-pr:
- gid: 948 - gid: 948
- home: /opt/so/conf/elastic-fleet-pr - home: /opt/so/conf/elastic-fleet-pr
- createhome: False - createhome: False
- shell: /sbin/nologin
{% else %} {% else %}
+2 -3
View File
@@ -20,7 +20,6 @@ elastic-agent:
- gid: 949 - gid: 949
- home: /opt/so/conf/elastic-agent - home: /opt/so/conf/elastic-agent
- createhome: False - createhome: False
- shell: /sbin/nologin
elasticagentconfdir: elasticagentconfdir:
file.directory: file.directory:
@@ -40,8 +39,8 @@ elasticagent_sbin_jinja:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://elasticagent/tools/sbin_jinja - source: salt://elasticagent/tools/sbin_jinja
- user: root - user: 949
- group: root - group: 939
- file_mode: 755 - file_mode: 755
- template: jinja - template: jinja
+6 -7
View File
@@ -26,14 +26,13 @@ elastic-fleet:
- gid: 947 - gid: 947
- home: /opt/so/conf/elastic-fleet - home: /opt/so/conf/elastic-fleet
- createhome: False - createhome: False
- shell: /sbin/nologin
elasticfleet_sbin: elasticfleet_sbin:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://elasticfleet/tools/sbin - source: salt://elasticfleet/tools/sbin
- user: root - user: 947
- group: root - group: 939
- file_mode: 755 - file_mode: 755
- show_changes: False - show_changes: False
@@ -41,8 +40,8 @@ elasticfleet_sbin_jinja:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://elasticfleet/tools/sbin_jinja - source: salt://elasticfleet/tools/sbin_jinja
- user: root - user: 947
- group: root - group: 939
- file_mode: 755 - file_mode: 755
- template: jinja - template: jinja
- exclude_pat: - exclude_pat:
@@ -82,8 +81,8 @@ eapackageupgrade:
file.managed: file.managed:
- name: /usr/sbin/so-elastic-fleet-package-upgrade - name: /usr/sbin/so-elastic-fleet-package-upgrade
- source: salt://elasticfleet/tools/sbin_jinja/so-elastic-fleet-package-upgrade - source: salt://elasticfleet/tools/sbin_jinja/so-elastic-fleet-package-upgrade
- user: root - user: 947
- group: root - group: 939
- mode: 755 - mode: 755
- template: jinja - template: jinja
+10
View File
@@ -11,6 +11,10 @@
{# This value is generated during node install and stored in minion pillar #} {# This value is generated during node install and stored in minion pillar #}
{% set SERVICETOKEN = salt['pillar.get']('elasticfleet:config:server:es_token','') %} {% set SERVICETOKEN = salt['pillar.get']('elasticfleet:config:server:es_token','') %}
{# Prevent Elastic Agent from re-enrolling with a new agent.id everytime the container starts up.
- if a fresh enrollment is needed use 'so-stop elasticfleet'
#}
{% set ENROLLED = salt['file.file_exists']('/opt/so/conf/elastic-fleet/state/fleet.enc') %}
include: include:
- ca - ca
@@ -66,6 +70,7 @@ so-elastic-fleet:
- /etc/pki/elasticfleet-server.crt:/etc/pki/elasticfleet-server.crt:ro - /etc/pki/elasticfleet-server.crt:/etc/pki/elasticfleet-server.crt:ro
- /etc/pki/elasticfleet-server.key:/etc/pki/elasticfleet-server.key:ro - /etc/pki/elasticfleet-server.key:/etc/pki/elasticfleet-server.key:ro
- /etc/pki/tls/certs/intca.crt:/etc/pki/tls/certs/intca.crt:ro - /etc/pki/tls/certs/intca.crt:/etc/pki/tls/certs/intca.crt:ro
- /opt/so/conf/elastic-fleet/state:/usr/share/elastic-agent/state
- /opt/so/log/elasticfleet:/usr/share/elastic-agent/logs - /opt/so/log/elasticfleet:/usr/share/elastic-agent/logs
{% if DOCKERMERGED.containers['so-elastic-fleet'].custom_bind_mounts %} {% if DOCKERMERGED.containers['so-elastic-fleet'].custom_bind_mounts %}
{% for BIND in DOCKERMERGED.containers['so-elastic-fleet'].custom_bind_mounts %} {% for BIND in DOCKERMERGED.containers['so-elastic-fleet'].custom_bind_mounts %}
@@ -73,6 +78,7 @@ so-elastic-fleet:
{% endfor %} {% endfor %}
{% endif %} {% endif %}
- environment: - environment:
{% if not ENROLLED %}
- FLEET_SERVER_ENABLE=true - FLEET_SERVER_ENABLE=true
- FLEET_URL=https://{{ GLOBALS.hostname }}:8220 - FLEET_URL=https://{{ GLOBALS.hostname }}:8220
- FLEET_SERVER_ELASTICSEARCH_HOST=https://{{ GLOBALS.manager }}:9200 - FLEET_SERVER_ELASTICSEARCH_HOST=https://{{ GLOBALS.manager }}:9200
@@ -82,6 +88,9 @@ so-elastic-fleet:
- FLEET_SERVER_CERT_KEY=/etc/pki/elasticfleet-server.key - FLEET_SERVER_CERT_KEY=/etc/pki/elasticfleet-server.key
- FLEET_CA=/etc/pki/tls/certs/intca.crt - FLEET_CA=/etc/pki/tls/certs/intca.crt
- FLEET_SERVER_ELASTICSEARCH_CA=/etc/pki/tls/certs/intca.crt - FLEET_SERVER_ELASTICSEARCH_CA=/etc/pki/tls/certs/intca.crt
{% endif %}
- STATE_PATH=/usr/share/elastic-agent/state
- CONFIG_PATH=/usr/share/elastic-agent/state
- LOGS_PATH=logs - LOGS_PATH=logs
{% if DOCKERMERGED.containers['so-elastic-fleet'].extra_env %} {% if DOCKERMERGED.containers['so-elastic-fleet'].extra_env %}
{% for XTRAENV in DOCKERMERGED.containers['so-elastic-fleet'].extra_env %} {% for XTRAENV in DOCKERMERGED.containers['so-elastic-fleet'].extra_env %}
@@ -100,6 +109,7 @@ so-elastic-fleet:
- x509: etc_elasticfleet_crt - x509: etc_elasticfleet_crt
- require: - require:
- file: trusttheca - file: trusttheca
- file: eastatedir
- x509: etc_elasticfleet_key - x509: etc_elasticfleet_key
- x509: etc_elasticfleet_crt - x509: etc_elasticfleet_crt
@@ -5,7 +5,7 @@
"package": { "package": {
"name": "endpoint", "name": "endpoint",
"title": "Elastic Defend", "title": "Elastic Defend",
"version": "9.4.1", "version": "9.3.1",
"requires_root": true "requires_root": true
}, },
"enabled": true, "enabled": true,
@@ -29,7 +29,7 @@
"\\.gz$" "\\.gz$"
], ],
"include_files": [], "include_files": [],
"processors": "- dissect:\n tokenizer: \"/nsm/import/%{import.id}/evtx/%{import.file}\"\n field: \"log.file.path\"\n target_prefix: \"\"\n- decode_json_fields:\n fields: [\"message\"]\n target: \"\"\n- drop_fields:\n fields: [\"host\"]\n ignore_missing: true\n- add_fields:\n target: data_stream\n fields:\n type: logs\n dataset: system.security\n- add_fields:\n target: event\n fields:\n dataset: system.security\n module: system\n imported: true\n- add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-system.security-2.22.3\n- if:\n equals:\n winlog.channel: 'Microsoft-Windows-Sysmon/Operational'\n then: \n - add_fields:\n target: data_stream\n fields:\n dataset: windows.sysmon_operational\n - add_fields:\n target: event\n fields:\n dataset: windows.sysmon_operational\n module: windows\n imported: true\n - add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-windows.sysmon_operational-3.9.0\n- if:\n equals:\n winlog.channel: 'Application'\n then: \n - add_fields:\n target: data_stream\n fields:\n dataset: system.application\n - add_fields:\n target: event\n fields:\n dataset: system.application\n - add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-system.application-2.22.3\n- if:\n equals:\n winlog.channel: 'System'\n then: \n - add_fields:\n target: data_stream\n fields:\n dataset: system.system\n - add_fields:\n target: event\n fields:\n dataset: system.system\n - add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-system.system-2.22.3\n \n- if:\n equals:\n winlog.channel: 'Microsoft-Windows-PowerShell/Operational'\n then: \n - add_fields:\n target: data_stream\n fields:\n dataset: windows.powershell_operational\n - add_fields:\n target: event\n fields:\n dataset: windows.powershell_operational\n module: windows\n - add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-windows.powershell_operational-3.9.0\n- add_fields:\n target: data_stream\n fields:\n dataset: import", "processors": "- dissect:\n tokenizer: \"/nsm/import/%{import.id}/evtx/%{import.file}\"\n field: \"log.file.path\"\n target_prefix: \"\"\n- decode_json_fields:\n fields: [\"message\"]\n target: \"\"\n- drop_fields:\n fields: [\"host\"]\n ignore_missing: true\n- add_fields:\n target: data_stream\n fields:\n type: logs\n dataset: system.security\n- add_fields:\n target: event\n fields:\n dataset: system.security\n module: system\n imported: true\n- add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-system.security-2.20.0\n- if:\n equals:\n winlog.channel: 'Microsoft-Windows-Sysmon/Operational'\n then: \n - add_fields:\n target: data_stream\n fields:\n dataset: windows.sysmon_operational\n - add_fields:\n target: event\n fields:\n dataset: windows.sysmon_operational\n module: windows\n imported: true\n - add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-windows.sysmon_operational-3.8.3\n- if:\n equals:\n winlog.channel: 'Application'\n then: \n - add_fields:\n target: data_stream\n fields:\n dataset: system.application\n - add_fields:\n target: event\n fields:\n dataset: system.application\n - add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-system.application-2.20.0\n- if:\n equals:\n winlog.channel: 'System'\n then: \n - add_fields:\n target: data_stream\n fields:\n dataset: system.system\n - add_fields:\n target: event\n fields:\n dataset: system.system\n - add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-system.system-2.20.0\n \n- if:\n equals:\n winlog.channel: 'Microsoft-Windows-PowerShell/Operational'\n then: \n - add_fields:\n target: data_stream\n fields:\n dataset: windows.powershell_operational\n - add_fields:\n target: event\n fields:\n dataset: windows.powershell_operational\n module: windows\n - add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-windows.powershell_operational-3.8.3\n- add_fields:\n target: data_stream\n fields:\n dataset: import",
"tags": [ "tags": [
"import" "import"
], ],
+2 -2
View File
@@ -14,8 +14,8 @@ so-elastic-agent-install:
file.managed: file.managed:
- name: /usr/sbin/so-elastic-agent-install - name: /usr/sbin/so-elastic-agent-install
- source: salt://elasticfleet/tools/sbin/so-elastic-agent-install - source: salt://elasticfleet/tools/sbin/so-elastic-agent-install
- user: root - user: 947
- group: root - group: 939
- mode: 755 - mode: 755
- show_changes: False - show_changes: False
@@ -16,6 +16,7 @@
'awsfirehose.metrics': 'aws.cloudwatch', 'awsfirehose.metrics': 'aws.cloudwatch',
'cribl.logs': 'cribl', 'cribl.logs': 'cribl',
'cribl.metrics': 'cribl', 'cribl.metrics': 'cribl',
'sentinel_one_cloud_funnel.logins': 'sentinel_one_cloud_funnel.login',
'azure_application_insights.app_insights': 'azure.app_insights', 'azure_application_insights.app_insights': 'azure.app_insights',
'azure_application_insights.app_state': 'azure.app_state', 'azure_application_insights.app_state': 'azure.app_state',
'azure_billing.billing': 'azure.billing', 'azure_billing.billing': 'azure.billing',
+9 -19
View File
@@ -68,24 +68,6 @@ so-elastic-fleet-package-upgrade:
- require: - require:
- http: wait_for_so-kibana - http: wait_for_so-kibana
# initial so-elasticsearch-templates run is earlier, but it can skip over templates that have component templates not yet installed to avoid elasticsearch rejecting the template.
so-elasticsearch-templates-after-fleet-packages:
cmd.run:
- name: /usr/sbin/so-elasticsearch-templates-load
- cwd: /opt/so
- unless: test -f /opt/so/state/estemplates.txt
- require:
- cmd: so-elastic-fleet-package-upgrade
so-elastic-fleet-integration-upgrade:
cmd.run:
- name: /usr/sbin/so-elastic-fleet-integration-upgrade
- retry:
attempts: 3
interval: 10
- require:
- cmd: so-elastic-fleet-package-upgrade
so-elastic-fleet-integrations: so-elastic-fleet-integrations:
cmd.run: cmd.run:
- name: /usr/sbin/so-elastic-fleet-integration-policy-load - name: /usr/sbin/so-elastic-fleet-integration-policy-load
@@ -104,13 +86,21 @@ so-elastic-agent-grid-upgrade:
- require: - require:
- http: wait_for_so-kibana - http: wait_for_so-kibana
so-elastic-fleet-integration-upgrade:
cmd.run:
- name: /usr/sbin/so-elastic-fleet-integration-upgrade
- retry:
attempts: 3
interval: 10
- require:
- http: wait_for_so-kibana
{# Optional integrations script doesn't need the retries like so-elastic-fleet-integration-upgrade which loads the default integrations #} {# Optional integrations script doesn't need the retries like so-elastic-fleet-integration-upgrade which loads the default integrations #}
so-elastic-fleet-addon-integrations: so-elastic-fleet-addon-integrations:
cmd.run: cmd.run:
- name: /usr/sbin/so-elastic-fleet-optional-integrations-load - name: /usr/sbin/so-elastic-fleet-optional-integrations-load
- require: - require:
- http: wait_for_so-kibana - http: wait_for_so-kibana
- cmd: so-elasticsearch-templates-after-fleet-packages
{% if ELASTICFLEETMERGED.config.defend_filters.enable_auto_configuration %} {% if ELASTICFLEETMERGED.config.defend_filters.enable_auto_configuration %}
so-elastic-defend-manage-filters-file-watch: so-elastic-defend-manage-filters-file-watch:
@@ -30,56 +30,6 @@ fleet_api() {
curl -sK /opt/so/conf/elasticsearch/curl.config -L "localhost:5601/api/fleet/${QUERYPATH}" "$@" --retry 3 --retry-delay 10 --fail 2>/dev/null curl -sK /opt/so/conf/elasticsearch/curl.config -L "localhost:5601/api/fleet/${QUERYPATH}" "$@" --retry 3 --retry-delay 10 --fail 2>/dev/null
} }
elastic_fleet_require_agent_policy() {
local AGENT_POLICY=$1
local POLICY_JSON
if ! POLICY_JSON=$(fleet_api "agent_policies/$AGENT_POLICY") || [ -z "$POLICY_JSON" ]; then
echo "Error: Agent policy '$AGENT_POLICY' was not found or is not visible in the current Kibana space." >&2
return 1
fi
if ! jq -e '.item.package_policies | type == "array"' <<<"$POLICY_JSON" >/dev/null 2>&1; then
echo "Error: Agent policy '$AGENT_POLICY' was not found or is not visible in the current Kibana space." >&2
return 1
fi
echo "$POLICY_JSON"
}
# Print the single active enrollment token for POLICY_ID.
# Exit 1: retryable (API failure, invalid response, no active token)
# Exit 2: multiple active tokens - Shouldn't get into this state without manual intervention
elastic_fleet_active_enrollment_token() {
local POLICY_ID=$1
local RESP TOKEN_COUNT API_KEY
if ! RESP=$(fleet_api "enrollment_api_keys?perPage=100" -H 'kbn-xsrf: true' -H 'Content-Type: application/json'); then
echo "Error: Failed to retrieve enrollment tokens for agent policy '$POLICY_ID'." >&2
return 1
fi
if ! jq -e '.list' <<<"$RESP" >/dev/null 2>&1; then
echo "Error: Invalid enrollment token response for agent policy '$POLICY_ID'." >&2
return 1
fi
TOKEN_COUNT=$(jq --arg pid "$POLICY_ID" '[.list[] | select(.policy_id == $pid and .active == true)] | length' <<<"$RESP")
if [ "${TOKEN_COUNT:-0}" -eq 0 ]; then
echo "Error: No active enrollment token found for agent policy '$POLICY_ID'." >&2
return 1
fi
if [ "$TOKEN_COUNT" -gt 1 ]; then
echo "Error: Found $TOKEN_COUNT active enrollment tokens for agent policy '$POLICY_ID'; expected exactly one." >&2
return 2
fi
API_KEY=$(jq -r --arg pid "$POLICY_ID" '.list[] | select(.policy_id == $pid and .active == true) | .api_key' <<<"$RESP")
echo "$API_KEY"
}
# Max number of concurrent Fleet write jobs (create/update). Override via env if needed. # Max number of concurrent Fleet write jobs (create/update). Override via env if needed.
MAX_FLEET_JOBS=${MAX_FLEET_JOBS:-10} MAX_FLEET_JOBS=${MAX_FLEET_JOBS:-10}
@@ -112,7 +62,15 @@ elastic_fleet_load_integrations_dir() {
i=0 i=0
# Fetch the agent policy a single time; we look up integration ids locally below. # Fetch the agent policy a single time; we look up integration ids locally below.
if ! POLICY_JSON=$(elastic_fleet_require_agent_policy "$AGENT_POLICY"); then if ! POLICY_JSON=$(fleet_api "agent_policies/$AGENT_POLICY"); then
echo "Error: Failed to retrieve agent policy '$AGENT_POLICY'."
rm -f "$FAIL_FILE"
rm -rf "$OUT_DIR"
return 1
fi
if ! jq -e '.item.package_policies' <<<"$POLICY_JSON" >/dev/null 2>&1; then
echo "Error: Invalid agent policy response for '$AGENT_POLICY'."
rm -f "$FAIL_FILE" rm -f "$FAIL_FILE"
rm -rf "$OUT_DIR" rm -rf "$OUT_DIR"
return 1 return 1
@@ -166,15 +124,9 @@ elastic_fleet_integration_check() {
JSON_STRING=$2 JSON_STRING=$2
NAME=$(jq -r .name "$JSON_STRING") NAME=$(jq -r .name $JSON_STRING)
INTEGRATION_ID=""
local POLICY_JSON INTEGRATION_ID=$(/usr/sbin/so-elastic-fleet-agent-policy-view "$AGENT_POLICY" | jq -r '.item.package_policies[] | select(.name=="'"$NAME"'") | .id')
if ! POLICY_JSON=$(elastic_fleet_require_agent_policy "$AGENT_POLICY"); then
return 1
fi
INTEGRATION_ID=$(jq -r --arg n "$NAME" '.item.package_policies[]? | select(.name==$n) | .id' <<<"$POLICY_JSON")
} }
@@ -196,16 +148,7 @@ elastic_fleet_integration_remove() {
NAME=$2 NAME=$2
local POLICY_JSON INTEGRATION_ID=$(/usr/sbin/so-elastic-fleet-agent-policy-view "$AGENT_POLICY" | jq -r '.item.package_policies[] | select(.name=="'"$NAME"'") | .id')
if ! POLICY_JSON=$(elastic_fleet_require_agent_policy "$AGENT_POLICY"); then
return 1
fi
INTEGRATION_ID=$(jq -r --arg n "$NAME" '.item.package_policies[]? | select(.name==$n) | .id' <<<"$POLICY_JSON")
if [ -z "$INTEGRATION_ID" ]; then
echo "Error: Integration '$NAME' was not found in agent policy '$AGENT_POLICY'." >&2
return 1
fi
JSON_STRING=$( jq -n \ JSON_STRING=$( jq -n \
--arg INTEGRATIONID "$INTEGRATION_ID" \ --arg INTEGRATIONID "$INTEGRATION_ID" \
@@ -13,10 +13,7 @@ ERROR=false
for INTEGRATION in /opt/so/conf/elastic-fleet/integrations/elastic-defend/*.json for INTEGRATION in /opt/so/conf/elastic-fleet/integrations/elastic-defend/*.json
do do
printf "\n\nInitial Endpoints Policy - Loading $INTEGRATION\n" printf "\n\nInitial Endpoints Policy - Loading $INTEGRATION\n"
if ! elastic_fleet_integration_check "endpoints-initial" "$INTEGRATION"; then elastic_fleet_integration_check "endpoints-initial" "$INTEGRATION"
ERROR=true
continue
fi
if [ -n "$INTEGRATION_ID" ]; then if [ -n "$INTEGRATION_ID" ]; then
printf "\n\nIntegration $NAME exists - Upgrading integration policy\n" printf "\n\nIntegration $NAME exists - Upgrading integration policy\n"
if ! elastic_fleet_integration_policy_upgrade "$INTEGRATION_ID"; then if ! elastic_fleet_integration_policy_upgrade "$INTEGRATION_ID"; then
@@ -7,35 +7,20 @@
. /usr/sbin/so-elastic-fleet-common . /usr/sbin/so-elastic-fleet-common
# Get all the fleet policies # Get all the fleet policies
if ! json_output=$(fleet_api "agent_policies" -H 'kbn-xsrf: true'); then json_output=$(curl -s -K /opt/so/conf/elasticsearch/curl.config -L -X GET "localhost:5601/api/fleet/agent_policies" -H 'kbn-xsrf: true')
echo "Error: Failed to retrieve Fleet agent policies." >&2
exit 1
fi
if ! jq -e '.items' <<<"$json_output" >/dev/null 2>&1; then
echo "Error: Invalid Fleet agent policies response." >&2
exit 1
fi
# Extract the IDs that start with "FleetServer_" # Extract the IDs that start with "FleetServer_"
POLICY=$(jq -r '.items[] | select(.id | startswith("FleetServer_")) | .id' <<<"$json_output") POLICY=$(echo "$json_output" | jq -r '.items[] | select(.id | startswith("FleetServer_")) | .id')
# Iterate over each ID in the POLICY variable # Iterate over each ID in the POLICY variable
for POLICYNAME in $POLICY; do for POLICYNAME in $POLICY; do
printf "\nUpdating Policy: $POLICYNAME\n" printf "\nUpdating Policy: $POLICYNAME\n"
if ! POLICY_JSON=$(elastic_fleet_require_agent_policy "$POLICYNAME"); then # First get the Integration ID
exit 1 INTEGRATION_ID=$(/usr/sbin/so-elastic-fleet-agent-policy-view "$POLICYNAME" | jq -r '.item.package_policies[] | select(.package.name == "fleet_server") | .id')
fi
INTEGRATION_ID=$(jq -r '.item.package_policies[]? | select(.package.name == "fleet_server") | .id' <<<"$POLICY_JSON")
if [ -z "$INTEGRATION_ID" ]; then
echo "Error: fleet_server integration was not found in agent policy '$POLICYNAME'." >&2
exit 1
fi
# Modify the default integration policy to update the policy_id and an with the correct naming # Modify the default integration policy to update the policy_id and an with the correct naming
UPDATED_INTEGRATION_POLICY=$(jq --arg policy_id "$POLICYNAME" --arg name "fleet_server-$POLICYNAME" ' UPDATED_INTEGRATION_POLICY=$(jq --arg policy_id "$POLICYNAME" --arg name "fleet_server-$POLICYNAME" '
.policy_id = $policy_id | .policy_id = $policy_id |
.name = $name' /opt/so/conf/elastic-fleet/integrations/fleet-server/fleet-server.json) .name = $name' /opt/so/conf/elastic-fleet/integrations/fleet-server/fleet-server.json)
@@ -22,19 +22,12 @@ NUM_RUNNING=$(pgrep -cf "/bin/bash /sbin/so-elastic-agent-gen-installers")
for i in {1..30} for i in {1..30}
do do
ENROLLMENTOKEN=$(elastic_fleet_active_enrollment_token "endpoints-initial") ENROLLMENTOKEN=$(curl -K /opt/so/conf/elasticsearch/curl.config -L "localhost:5601/api/fleet/enrollment_api_keys?perPage=100" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' | jq .list | jq -r -c '.[] | select(.policy_id | contains("endpoints-initial")) | .api_key')
TOKEN_RC=$?
if [ "$TOKEN_RC" -eq 2 ]; then
exit 1
fi
FLEETHOST=$(curl -K /opt/so/conf/elasticsearch/curl.config 'http://localhost:5601/api/fleet/fleet_server_hosts/grid-default' | jq -r '.item.host_urls[]' | paste -sd ',') FLEETHOST=$(curl -K /opt/so/conf/elasticsearch/curl.config 'http://localhost:5601/api/fleet/fleet_server_hosts/grid-default' | jq -r '.item.host_urls[]' | paste -sd ',')
if [[ -n "$FLEETHOST" ]] && [[ -n "$ENROLLMENTOKEN" ]]; then if [[ $FLEETHOST ]] && [[ $ENROLLMENTOKEN ]]; then break; else sleep 10; fi
break
fi
sleep 10
done done
if [[ -z "$FLEETHOST" ]] || [[ -z "$ENROLLMENTOKEN" ]]; then if [[ -z $FLEETHOST ]] || [[ -z $ENROLLMENTOKEN ]]; then
printf "\nFleet Host URL, Enrollment Token or Elastic Version empty - exiting..." printf "\nFleet Host URL, Enrollment Token or Elastic Version empty - exiting..."
printf "\nFleet Host: $FLEETHOST, Enrollment Token: $ENROLLMENTOKEN\n" printf "\nFleet Host: $FLEETHOST, Enrollment Token: $ENROLLMENTOKEN\n"
exit 1 exit 1
@@ -74,25 +67,19 @@ for GOOS in "${GOTARGETOS[@]}"; do
GOARCH="amd64" GOARCH="amd64"
if [[ $GOOS == 'darwin/arm64' ]]; then GOOS="darwin" && GOARCH="arm64"; fi if [[ $GOOS == 'darwin/arm64' ]]; then GOOS="darwin" && GOARCH="arm64"; fi
printf "\n\n### Generating $GOOS/$GOARCH Installer...\n" printf "\n\n### Generating $GOOS/$GOARCH Installer...\n"
if ! docker run -e CGO_ENABLED=0 -e GOOS=$GOOS -e GOARCH=$GOARCH \ docker run -e CGO_ENABLED=0 -e GOOS=$GOOS -e GOARCH=$GOARCH \
--mount type=bind,source=/etc/pki/tls/certs/,target=/workspace/files/cert/ \ --mount type=bind,source=/etc/pki/tls/certs/,target=/workspace/files/cert/ \
--mount type=bind,source=/nsm/elastic-agent-workspace/,target=/workspace/files/elastic-agent/ \ --mount type=bind,source=/nsm/elastic-agent-workspace/,target=/workspace/files/elastic-agent/ \
--mount type=bind,source=/opt/so/saltstack/local/salt/elasticfleet/files/,target=/output/ \ --mount type=bind,source=/opt/so/saltstack/local/salt/elasticfleet/files/,target=/output/ \
{{ GLOBALS.registry_host }}:5000/{{ GLOBALS.image_repo }}/so-elastic-agent-builder:{{ GLOBALS.so_version }} go build -ldflags "-X main.fleetHostURLsList=$FLEETHOST -X main.enrollmentToken=$ENROLLMENTOKEN" -o /output/so-elastic-agent_${GOOS}_${GOARCH}; then {{ GLOBALS.registry_host }}:5000/{{ GLOBALS.image_repo }}/so-elastic-agent-builder:{{ GLOBALS.so_version }} go build -ldflags "-X main.fleetHostURLsList=$FLEETHOST -X main.enrollmentToken=$ENROLLMENTOKEN" -o /output/so-elastic-agent_${GOOS}_${GOARCH}
printf "\n### ERROR: Failed to generate $GOOS/$GOARCH installer. Exiting...\n"
exit 1
fi
printf "\n### $GOOS/$GOARCH Installer Generated...\n" printf "\n### $GOOS/$GOARCH Installer Generated...\n"
done done
printf "\n\n### Generating MSI...\n" printf "\n\n### Generating MSI...\n"
cp /opt/so/saltstack/local/salt/elasticfleet/files/so-elastic-agent_windows_amd64 /opt/so/saltstack/local/salt/elasticfleet/files/so-elastic-agent_windows_amd64.exe cp /opt/so/saltstack/local/salt/elasticfleet/files/so-elastic-agent_windows_amd64 /opt/so/saltstack/local/salt/elasticfleet/files/so-elastic-agent_windows_amd64.exe
if ! docker run \ docker run \
--mount type=bind,source=/opt/so/saltstack/local/salt/elasticfleet/files/,target=/output/ -w /output \ --mount type=bind,source=/opt/so/saltstack/local/salt/elasticfleet/files/,target=/output/ -w /output \
{{ GLOBALS.registry_host }}:5000/{{ GLOBALS.image_repo }}/so-elastic-agent-builder:{{ GLOBALS.so_version }} wixl -o so-elastic-agent_windows_amd64_msi --arch x64 /workspace/so-elastic-agent.wxs; then {{ GLOBALS.registry_host }}:5000/{{ GLOBALS.image_repo }}/so-elastic-agent-builder:{{ GLOBALS.so_version }} wixl -o so-elastic-agent_windows_amd64_msi --arch x64 /workspace/so-elastic-agent.wxs
printf "\n### ERROR: Failed to generate MSI. Exiting...\n"
exit 1
fi
printf "\n### MSI Generated...\n" printf "\n### MSI Generated...\n"
# Verify installers were created # Verify installers were created
@@ -10,25 +10,6 @@
PKG_LOAD_FAILURES=0 PKG_LOAD_FAILURES=0
PKG_LOAD_FAILURES_NAMES=() PKG_LOAD_FAILURES_NAMES=()
PKG_UPGRADED=0
cleanup_elasticsearch_fleet_transforms() {
local transforms transform_id attempt
if ! transforms=$(so-elasticsearch-query "_transform/logs-elasticsearch.index_pivot-default-*" --retry 1 --retry-delay 5); then
return 0
fi
while IFS= read -r transform_id; do
[ -n "$transform_id" ] || continue
for attempt in {1..3}; do
if so-elasticsearch-query "_transform/$transform_id?force=true" -XDELETE --fail --retry 1 --retry-delay 5; then
break
fi
sleep 5
done
done < <(jq -r '.transforms[]?.id' <<< "$transforms")
}
{%- for PACKAGE in SUPPORTED_PACKAGES %} {%- for PACKAGE in SUPPORTED_PACKAGES %}
if INSTALLED_VERSION=$(elastic_fleet_package_version_check "{{ PACKAGE }}") && LATEST_VERSION=$(elastic_fleet_package_latest_version_check "{{ PACKAGE }}"); then if INSTALLED_VERSION=$(elastic_fleet_package_version_check "{{ PACKAGE }}") && LATEST_VERSION=$(elastic_fleet_package_latest_version_check "{{ PACKAGE }}"); then
@@ -36,25 +17,10 @@ if INSTALLED_VERSION=$(elastic_fleet_package_version_check "{{ PACKAGE }}") && L
if [ "$INSTALLED_VERSION" == "$LATEST_VERSION" ]; then if [ "$INSTALLED_VERSION" == "$LATEST_VERSION" ]; then
echo "{{ PACKAGE }} integration version $INSTALLED_VERSION is already at the reported latest version $LATEST_VERSION, skipping upgrade." echo "{{ PACKAGE }} integration version $INSTALLED_VERSION is already at the reported latest version $LATEST_VERSION, skipping upgrade."
else else
{%- if PACKAGE == 'elasticsearch' %} echo "Upgrading {{ PACKAGE }} package to version $LATEST_VERSION..."
cleanup_elasticsearch_fleet_transforms
{%- endif %}
echo "Upgrading {{ PACKAGE }} package from $INSTALLED_VERSION to version $LATEST_VERSION..."
if ! elastic_fleet_package_install "{{ PACKAGE }}" "$LATEST_VERSION"; then if ! elastic_fleet_package_install "{{ PACKAGE }}" "$LATEST_VERSION"; then
PKG_LOAD_FAILURES=$((PKG_LOAD_FAILURES + 1)) PKG_LOAD_FAILURES=$((PKG_LOAD_FAILURES + 1))
PKG_LOAD_FAILURES_NAMES+=("{{ PACKAGE }}") PKG_LOAD_FAILURES_NAMES+=("{{ PACKAGE }}")
# check that package has upgraded to the expected version after install command
elif ! LATEST_VERSION=$(elastic_fleet_package_latest_version_check "{{ PACKAGE }}"); then
echo "ERROR: Failed to get latest version information for integration {{ PACKAGE }} after upgrade attempt"
PKG_LOAD_FAILURES=$((PKG_LOAD_FAILURES + 1))
PKG_LOAD_FAILURES_NAMES+=("{{ PACKAGE }}")
elif INSTALLED_VERSION=$(elastic_fleet_package_version_check "{{ PACKAGE }}") && [ "$INSTALLED_VERSION" == "$LATEST_VERSION" ]; then
echo "{{ PACKAGE }} integration upgraded to version $LATEST_VERSION."
PKG_UPGRADED=$((PKG_UPGRADED + 1))
else
echo "ERROR: {{ PACKAGE }} integration still at ${INSTALLED_VERSION:-unknown}; expected $LATEST_VERSION"
PKG_LOAD_FAILURES=$((PKG_LOAD_FAILURES + 1))
PKG_LOAD_FAILURES_NAMES+=("{{ PACKAGE }}")
fi fi
fi fi
else else
@@ -64,11 +30,6 @@ else
fi fi
{%- endfor %} {%- endfor %}
if [ $PKG_UPGRADED -gt 0 ]; then
echo "Elasticsearch template statefiles cleared after $PKG_UPGRADED package upgrade(s), so templates can reload."
rm -f /opt/so/state/estemplates.txt /opt/so/state/addon_estemplates.txt
fi
if [ $PKG_LOAD_FAILURES -gt 0 ]; then if [ $PKG_LOAD_FAILURES -gt 0 ]; then
echo "ERROR: Failed to upgrade $PKG_LOAD_FAILURES package(s):" echo "ERROR: Failed to upgrade $PKG_LOAD_FAILURES package(s):"
for PKG in "${PKG_LOAD_FAILURES_NAMES[@]}"; do for PKG in "${PKG_LOAD_FAILURES_NAMES[@]}"; do
@@ -202,9 +202,26 @@ fi
### Finalization ### ### Finalization ###
# Query for Enrollment Tokens for default policies # Query for Enrollment Tokens for default policies
ENDPOINTSENROLLMENTOKEN=$(elastic_fleet_active_enrollment_token "endpoints-initial") || exit 1 if ENDPOINTSENROLLMENTOKEN_RAW=$(fleet_api "enrollment_api_keys" -H 'kbn-xsrf: true' -H 'Content-Type: application/json'); then
GRIDNODESENROLLMENTOKENGENERAL=$(elastic_fleet_active_enrollment_token "so-grid-nodes_general") || exit 1 ENDPOINTSENROLLMENTOKEN=$(echo "$ENDPOINTSENROLLMENTOKEN_RAW" | jq .list | jq -r -c '.[] | select(.policy_id | contains("endpoints-initial")) | .api_key')
GRIDNODESENROLLMENTOKENHEAVY=$(elastic_fleet_active_enrollment_token "so-grid-nodes_heavy") || exit 1 else
echo -e "\nFailed to query for Endpoints enrollment token"
exit 1
fi
if GRIDNODESENROLLMENTOKENGENERAL_RAW=$(fleet_api "enrollment_api_keys" -H 'kbn-xsrf: true' -H 'Content-Type: application/json'); then
GRIDNODESENROLLMENTOKENGENERAL=$(echo "$GRIDNODESENROLLMENTOKENGENERAL_RAW" | jq .list | jq -r -c '.[] | select(.policy_id | contains("so-grid-nodes_general")) | .api_key')
else
echo -e "\nFailed to query for Grid nodes - General enrollment token"
exit 1
fi
if GRIDNODESENROLLMENTOKENHEAVY_RAW=$(fleet_api "enrollment_api_keys" -H 'kbn-xsrf: true' -H 'Content-Type: application/json'); then
GRIDNODESENROLLMENTOKENHEAVY=$(echo "$GRIDNODESENROLLMENTOKENHEAVY_RAW" | jq .list | jq -r -c '.[] | select(.policy_id | contains("so-grid-nodes_heavy")) | .api_key')
else
echo -e "\nFailed to query for Grid nodes - Heavy enrollment token"
exit 1
fi
# Store needed data in minion pillar # Store needed data in minion pillar
pillar_file=/opt/so/saltstack/local/pillar/minions/{{ GLOBALS.minion_id }}.sls pillar_file=/opt/so/saltstack/local/pillar/minions/{{ GLOBALS.minion_id }}.sls
-7
View File
@@ -98,13 +98,6 @@ so-es-cluster-settings:
- docker_container: so-elasticsearch - docker_container: so-elasticsearch
- file: elasticsearch_sbin_jinja - file: elasticsearch_sbin_jinja
- http: wait_for_so-elasticsearch - http: wait_for_so-elasticsearch
so-elasticsearch-system-indices-patch:
cmd.run:
- name: /usr/sbin/so-elasticsearch-system-indices-patch
- require:
- http: wait_for_so-elasticsearch
- file: so-elasticsearch-system-indices-patch-script
{% endif %} {% endif %}
# heavynodes will only load ILM policies for SO managed indices. (Indicies defined in elasticsearch/defaults.yaml) # heavynodes will only load ILM policies for SO managed indices. (Indicies defined in elasticsearch/defaults.yaml)
+8 -19
View File
@@ -32,35 +32,24 @@ elasticsearch:
- gid: 930 - gid: 930
- home: /opt/so/conf/elasticsearch - home: /opt/so/conf/elasticsearch
- createhome: False - createhome: False
- shell: /sbin/nologin
elasticsearch_sbin: elasticsearch_sbin:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://elasticsearch/tools/sbin - source: salt://elasticsearch/tools/sbin
- user: root - user: 930
- group: root - group: 939
- file_mode: 755 - file_mode: 755
- exclude_pat: - exclude_pat:
- so-elasticsearch-pipelines # exclude this because we need to watch it for changes, we sync it in another state - so-elasticsearch-pipelines # exclude this because we need to watch it for changes, we sync it in another state
- so-elasticsearch-system-indices-patch
- show_changes: False
so-elasticsearch-system-indices-patch-script:
file.managed:
- name: /usr/sbin/so-elasticsearch-system-indices-patch
- source: salt://elasticsearch/tools/sbin/so-elasticsearch-system-indices-patch
- user: root
- group: root
- mode: 755
- show_changes: False - show_changes: False
elasticsearch_sbin_jinja: elasticsearch_sbin_jinja:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://elasticsearch/tools/sbin_jinja - source: salt://elasticsearch/tools/sbin_jinja
- user: root - user: 939
- group: root - group: 939
- file_mode: 755 - file_mode: 755
- template: jinja - template: jinja
- exclude_pat: - exclude_pat:
@@ -73,8 +62,8 @@ so-elasticsearch-ilm-policy-load-script:
file.managed: file.managed:
- name: /usr/sbin/so-elasticsearch-ilm-policy-load - name: /usr/sbin/so-elasticsearch-ilm-policy-load
- source: salt://elasticsearch/tools/sbin_jinja/so-elasticsearch-ilm-policy-load - source: salt://elasticsearch/tools/sbin_jinja/so-elasticsearch-ilm-policy-load
- user: root - user: 930
- group: root - group: 939
- mode: 754 - mode: 754
- template: jinja - template: jinja
- defaults: - defaults:
@@ -85,8 +74,8 @@ so-elasticsearch-pipelines-script:
file.managed: file.managed:
- name: /usr/sbin/so-elasticsearch-pipelines - name: /usr/sbin/so-elasticsearch-pipelines
- source: salt://elasticsearch/tools/sbin/so-elasticsearch-pipelines - source: salt://elasticsearch/tools/sbin/so-elasticsearch-pipelines
- user: root - user: 930
- group: root - group: 939
- mode: 754 - mode: 754
- show_changes: False - show_changes: False
+1 -715
View File
@@ -1,7 +1,7 @@
elasticsearch: elasticsearch:
enabled: false enabled: false
esheap: '600m' esheap: '600m'
version: 9.4.5 version: 9.3.7
index_clean: true index_clean: true
data_retention_method: DLM data_retention_method: DLM
vm: vm:
@@ -3454,720 +3454,6 @@ elasticsearch:
set_priority: set_priority:
priority: 50 priority: 50
min_age: 30d min_age: 30d
so-metrics-system_x_core:
data_stream_lifecycle:
data_retention: 90d
index_template:
composed_of:
- metrics@tsdb-settings
- metrics-system.core@package
- metrics@custom
- system@custom
- metrics-system.core@custom
- ecs@mappings
- so-fleet_integrations.ip_mappings-1
- so-fleet_globals-1
- so-fleet_agent_id_verification-1
data_stream:
allow_custom_routing: false
hidden: false
ignore_missing_component_templates:
- metrics@custom
- system@custom
- metrics-system.core@custom
index_patterns:
- metrics-system.core-*
priority: 501
template:
settings:
index:
lifecycle:
name: so-metrics-system.core-logs
mode: time_series
number_of_replicas: 0
policy:
phases:
cold:
actions:
set_priority:
priority: 0
min_age: 60d
delete:
actions:
delete: {}
min_age: 365d
hot:
actions:
rollover:
max_age: 30d
max_primary_shard_size: 50gb
set_priority:
priority: 100
min_age: 0ms
warm:
actions:
set_priority:
priority: 50
min_age: 30d
so-metrics-system_x_cpu:
data_stream_lifecycle:
data_retention: 90d
index_template:
composed_of:
- metrics@tsdb-settings
- metrics-system.cpu@package
- metrics@custom
- system@custom
- metrics-system.cpu@custom
- ecs@mappings
- so-fleet_integrations.ip_mappings-1
- so-fleet_globals-1
- so-fleet_agent_id_verification-1
data_stream:
allow_custom_routing: false
hidden: false
ignore_missing_component_templates:
- metrics@custom
- system@custom
- metrics-system.cpu@custom
index_patterns:
- metrics-system.cpu-*
priority: 501
template:
settings:
index:
lifecycle:
name: so-metrics-system.cpu-logs
mode: time_series
number_of_replicas: 0
policy:
phases:
cold:
actions:
set_priority:
priority: 0
min_age: 60d
delete:
actions:
delete: {}
min_age: 365d
hot:
actions:
rollover:
max_age: 30d
max_primary_shard_size: 50gb
set_priority:
priority: 100
min_age: 0ms
warm:
actions:
set_priority:
priority: 50
min_age: 30d
so-metrics-system_x_diskio:
data_stream_lifecycle:
data_retention: 90d
index_template:
composed_of:
- metrics@tsdb-settings
- metrics-system.diskio@package
- metrics@custom
- system@custom
- metrics-system.diskio@custom
- ecs@mappings
- so-fleet_integrations.ip_mappings-1
- so-fleet_globals-1
- so-fleet_agent_id_verification-1
data_stream:
allow_custom_routing: false
hidden: false
ignore_missing_component_templates:
- metrics@custom
- system@custom
- metrics-system.diskio@custom
index_patterns:
- metrics-system.diskio-*
priority: 501
template:
settings:
index:
lifecycle:
name: so-metrics-system.diskio-logs
mode: time_series
number_of_replicas: 0
policy:
phases:
cold:
actions:
set_priority:
priority: 0
min_age: 60d
delete:
actions:
delete: {}
min_age: 365d
hot:
actions:
rollover:
max_age: 30d
max_primary_shard_size: 50gb
set_priority:
priority: 100
min_age: 0ms
warm:
actions:
set_priority:
priority: 50
min_age: 30d
so-metrics-system_x_filesystem:
data_stream_lifecycle:
data_retention: 90d
index_template:
composed_of:
- metrics@tsdb-settings
- metrics-system.filesystem@package
- metrics@custom
- system@custom
- metrics-system.filesystem@custom
- ecs@mappings
- so-fleet_integrations.ip_mappings-1
- so-fleet_globals-1
- so-fleet_agent_id_verification-1
data_stream:
allow_custom_routing: false
hidden: false
ignore_missing_component_templates:
- metrics@custom
- system@custom
- metrics-system.filesystem@custom
index_patterns:
- metrics-system.filesystem-*
priority: 501
template:
settings:
index:
lifecycle:
name: so-metrics-system.filesystem-logs
mode: time_series
number_of_replicas: 0
policy:
phases:
cold:
actions:
set_priority:
priority: 0
min_age: 60d
delete:
actions:
delete: {}
min_age: 365d
hot:
actions:
rollover:
max_age: 30d
max_primary_shard_size: 50gb
set_priority:
priority: 100
min_age: 0ms
warm:
actions:
set_priority:
priority: 50
min_age: 30d
so-metrics-system_x_fsstat:
data_stream_lifecycle:
data_retention: 90d
index_template:
composed_of:
- metrics@tsdb-settings
- metrics-system.fsstat@package
- metrics@custom
- system@custom
- metrics-system.fsstat@custom
- ecs@mappings
- so-fleet_integrations.ip_mappings-1
- so-fleet_globals-1
- so-fleet_agent_id_verification-1
data_stream:
allow_custom_routing: false
hidden: false
ignore_missing_component_templates:
- metrics@custom
- system@custom
- metrics-system.fsstat@custom
index_patterns:
- metrics-system.fsstat-*
priority: 501
template:
settings:
index:
lifecycle:
name: so-metrics-system.fsstat-logs
mode: time_series
number_of_replicas: 0
policy:
phases:
cold:
actions:
set_priority:
priority: 0
min_age: 60d
delete:
actions:
delete: {}
min_age: 365d
hot:
actions:
rollover:
max_age: 30d
max_primary_shard_size: 50gb
set_priority:
priority: 100
min_age: 0ms
warm:
actions:
set_priority:
priority: 50
min_age: 30d
so-metrics-system_x_load:
data_stream_lifecycle:
data_retention: 90d
index_template:
composed_of:
- metrics@tsdb-settings
- metrics-system.load@package
- metrics@custom
- system@custom
- metrics-system.load@custom
- ecs@mappings
- so-fleet_integrations.ip_mappings-1
- so-fleet_globals-1
- so-fleet_agent_id_verification-1
data_stream:
allow_custom_routing: false
hidden: false
ignore_missing_component_templates:
- metrics@custom
- system@custom
- metrics-system.load@custom
index_patterns:
- metrics-system.load-*
priority: 501
template:
settings:
index:
lifecycle:
name: so-metrics-system.load-logs
mode: time_series
number_of_replicas: 0
policy:
phases:
cold:
actions:
set_priority:
priority: 0
min_age: 60d
delete:
actions:
delete: {}
min_age: 365d
hot:
actions:
rollover:
max_age: 30d
max_primary_shard_size: 50gb
set_priority:
priority: 100
min_age: 0ms
warm:
actions:
set_priority:
priority: 50
min_age: 30d
so-metrics-system_x_memory:
data_stream_lifecycle:
data_retention: 90d
index_template:
composed_of:
- metrics@tsdb-settings
- metrics-system.memory@package
- metrics@custom
- system@custom
- metrics-system.memory@custom
- ecs@mappings
- so-fleet_integrations.ip_mappings-1
- so-fleet_globals-1
- so-fleet_agent_id_verification-1
data_stream:
allow_custom_routing: false
hidden: false
ignore_missing_component_templates:
- metrics@custom
- system@custom
- metrics-system.memory@custom
index_patterns:
- metrics-system.memory-*
priority: 501
template:
settings:
index:
lifecycle:
name: so-metrics-system.memory-logs
mode: time_series
number_of_replicas: 0
policy:
phases:
cold:
actions:
set_priority:
priority: 0
min_age: 60d
delete:
actions:
delete: {}
min_age: 365d
hot:
actions:
rollover:
max_age: 30d
max_primary_shard_size: 50gb
set_priority:
priority: 100
min_age: 0ms
warm:
actions:
set_priority:
priority: 50
min_age: 30d
so-metrics-system_x_network:
data_stream_lifecycle:
data_retention: 90d
index_template:
composed_of:
- metrics@tsdb-settings
- metrics-system.network@package
- metrics@custom
- system@custom
- metrics-system.network@custom
- ecs@mappings
- so-fleet_integrations.ip_mappings-1
- so-fleet_globals-1
- so-fleet_agent_id_verification-1
data_stream:
allow_custom_routing: false
hidden: false
ignore_missing_component_templates:
- metrics@custom
- system@custom
- metrics-system.network@custom
index_patterns:
- metrics-system.network-*
priority: 501
template:
settings:
index:
lifecycle:
name: so-metrics-system.network-logs
mode: time_series
number_of_replicas: 0
policy:
phases:
cold:
actions:
set_priority:
priority: 0
min_age: 60d
delete:
actions:
delete: {}
min_age: 365d
hot:
actions:
rollover:
max_age: 30d
max_primary_shard_size: 50gb
set_priority:
priority: 100
min_age: 0ms
warm:
actions:
set_priority:
priority: 50
min_age: 30d
so-metrics-system_x_ntp:
data_stream_lifecycle:
data_retention: 90d
index_template:
composed_of:
- metrics@settings
- metrics-system.ntp@package
- metrics@custom
- system@custom
- metrics-system.ntp@custom
- ecs@mappings
- so-fleet_integrations.ip_mappings-1
- so-fleet_globals-1
- so-fleet_agent_id_verification-1
data_stream:
allow_custom_routing: false
hidden: false
ignore_missing_component_templates:
- metrics@custom
- system@custom
- metrics-system.ntp@custom
index_patterns:
- metrics-system.ntp-*
priority: 501
template:
settings:
index:
lifecycle:
name: so-metrics-system.ntp-logs
number_of_replicas: 0
policy:
phases:
cold:
actions:
set_priority:
priority: 0
min_age: 60d
delete:
actions:
delete: {}
min_age: 365d
hot:
actions:
rollover:
max_age: 30d
max_primary_shard_size: 50gb
set_priority:
priority: 100
min_age: 0ms
warm:
actions:
set_priority:
priority: 50
min_age: 30d
so-metrics-system_x_process:
data_stream_lifecycle:
data_retention: 90d
index_template:
composed_of:
- metrics@tsdb-settings
- metrics-system.process@package
- metrics@custom
- system@custom
- metrics-system.process@custom
- ecs@mappings
- so-fleet_integrations.ip_mappings-1
- so-fleet_globals-1
- so-fleet_agent_id_verification-1
data_stream:
allow_custom_routing: false
hidden: false
ignore_missing_component_templates:
- metrics@custom
- system@custom
- metrics-system.process@custom
index_patterns:
- metrics-system.process-*
priority: 501
template:
settings:
index:
lifecycle:
name: so-metrics-system.process-logs
mode: time_series
number_of_replicas: 0
policy:
phases:
cold:
actions:
set_priority:
priority: 0
min_age: 60d
delete:
actions:
delete: {}
min_age: 365d
hot:
actions:
rollover:
max_age: 30d
max_primary_shard_size: 50gb
set_priority:
priority: 100
min_age: 0ms
warm:
actions:
set_priority:
priority: 50
min_age: 30d
so-metrics-system_x_process_x_summary:
data_stream_lifecycle:
data_retention: 90d
index_template:
composed_of:
- metrics@tsdb-settings
- metrics-system.process.summary@package
- metrics@custom
- system@custom
- metrics-system.process.summary@custom
- ecs@mappings
- so-fleet_integrations.ip_mappings-1
- so-fleet_globals-1
- so-fleet_agent_id_verification-1
data_stream:
allow_custom_routing: false
hidden: false
ignore_missing_component_templates:
- metrics@custom
- system@custom
- metrics-system.process.summary@custom
index_patterns:
- metrics-system.process.summary-*
priority: 501
template:
settings:
index:
lifecycle:
name: so-metrics-system.process.summary-logs
mode: time_series
number_of_replicas: 0
policy:
phases:
cold:
actions:
set_priority:
priority: 0
min_age: 60d
delete:
actions:
delete: {}
min_age: 365d
hot:
actions:
rollover:
max_age: 30d
max_primary_shard_size: 50gb
set_priority:
priority: 100
min_age: 0ms
warm:
actions:
set_priority:
priority: 50
min_age: 30d
so-metrics-system_x_socket_summary:
data_stream_lifecycle:
data_retention: 90d
index_template:
composed_of:
- metrics@tsdb-settings
- metrics-system.socket_summary@package
- metrics@custom
- system@custom
- metrics-system.socket_summary@custom
- ecs@mappings
- so-fleet_integrations.ip_mappings-1
- so-fleet_globals-1
- so-fleet_agent_id_verification-1
data_stream:
allow_custom_routing: false
hidden: false
ignore_missing_component_templates:
- metrics@custom
- system@custom
- metrics-system.socket_summary@custom
index_patterns:
- metrics-system.socket_summary-*
priority: 501
template:
settings:
index:
lifecycle:
name: so-metrics-system.socket_summary-logs
mode: time_series
number_of_replicas: 0
policy:
phases:
cold:
actions:
set_priority:
priority: 0
min_age: 60d
delete:
actions:
delete: {}
min_age: 365d
hot:
actions:
rollover:
max_age: 30d
max_primary_shard_size: 50gb
set_priority:
priority: 100
min_age: 0ms
warm:
actions:
set_priority:
priority: 50
min_age: 30d
so-metrics-system_x_uptime:
data_stream_lifecycle:
data_retention: 90d
index_template:
composed_of:
- metrics@tsdb-settings
- metrics-system.uptime@package
- metrics@custom
- system@custom
- metrics-system.uptime@custom
- ecs@mappings
- so-fleet_integrations.ip_mappings-1
- so-fleet_globals-1
- so-fleet_agent_id_verification-1
data_stream:
allow_custom_routing: false
hidden: false
ignore_missing_component_templates:
- metrics@custom
- system@custom
- metrics-system.uptime@custom
index_patterns:
- metrics-system.uptime-*
priority: 501
template:
settings:
index:
lifecycle:
name: so-metrics-system.uptime-logs
mode: time_series
number_of_replicas: 0
policy:
phases:
cold:
actions:
set_priority:
priority: 0
min_age: 60d
delete:
actions:
delete: {}
min_age: 365d
hot:
actions:
rollover:
max_age: 30d
max_primary_shard_size: 50gb
set_priority:
priority: 100
min_age: 0ms
warm:
actions:
set_priority:
priority: 50
min_age: 30d
so-logs-windows_x_forwarded: so-logs-windows_x_forwarded:
index_sorting: false index_sorting: false
data_stream_lifecycle: data_stream_lifecycle:
@@ -5,8 +5,7 @@
{ "rename": { "field": "message2.proto", "target_field": "network.transport", "ignore_missing": true } }, { "rename": { "field": "message2.proto", "target_field": "network.transport", "ignore_missing": true } },
{ "rename": { "field": "message2.app_proto", "target_field": "network.protocol", "ignore_missing": true } }, { "rename": { "field": "message2.app_proto", "target_field": "network.protocol", "ignore_missing": true } },
{ "rename": { "field": "message2.fileinfo.filename", "target_field": "file.name", "ignore_missing": true } }, { "rename": { "field": "message2.fileinfo.filename", "target_field": "file.name", "ignore_missing": true } },
{ "rename": { "field": "message2.fileinfo.gaps", "target_field": "suricata.fileinfo.gaps", "ignore_missing": true } }, { "rename": { "field": "message2.fileinfo.gaps", "target_field": "file.bytes.missing", "ignore_missing": true } },
{ "set": { "if": "ctx.suricata?.fileinfo?.gaps == false", "field": "file.bytes.missing", "value": 0 } },
{ "rename": { "field": "message2.fileinfo.magic", "target_field": "file.mime_type", "ignore_missing": true } }, { "rename": { "field": "message2.fileinfo.magic", "target_field": "file.mime_type", "ignore_missing": true } },
{ "rename": { "field": "message2.fileinfo.md5", "target_field": "hash.md5", "ignore_missing": true } }, { "rename": { "field": "message2.fileinfo.md5", "target_field": "hash.md5", "ignore_missing": true } },
{ "rename": { "field": "message2.fileinfo.sha1", "target_field": "hash.sha1", "ignore_missing": true } }, { "rename": { "field": "message2.fileinfo.sha1", "target_field": "hash.sha1", "ignore_missing": true } },
+1 -2
View File
@@ -20,8 +20,7 @@ appender.rolling.strategy.type = DefaultRolloverStrategy
appender.rolling.strategy.action.type = Delete appender.rolling.strategy.action.type = Delete
appender.rolling.strategy.action.basepath = /var/log/elasticsearch appender.rolling.strategy.action.basepath = /var/log/elasticsearch
appender.rolling.strategy.action.condition.type = IfFileName appender.rolling.strategy.action.condition.type = IfFileName
# age delete regular securityonion.log.gz and gc.log.NN files appender.rolling.strategy.action.condition.glob = *.log.gz
appender.rolling.strategy.action.condition.regex = (?:.*[.]log[.]gz|gc[.]log[.][0-9]+)
appender.rolling.strategy.action.condition.nested_condition.type = IfLastModified appender.rolling.strategy.action.condition.nested_condition.type = IfLastModified
appender.rolling.strategy.action.condition.nested_condition.age = 7D appender.rolling.strategy.action.condition.nested_condition.age = 7D
+13 -57
View File
@@ -64,43 +64,6 @@ elasticsearch:
flood_stage: flood_stage:
description: The max percentage of used disk space that will cause the node to take protective actions, such as blocking incoming events. description: The max percentage of used disk space that will cause the node to take protective actions, such as blocking incoming events.
helpLink: elasticsearch helpLink: elasticsearch
lifecycle:
default:
rollover:
description: This property accepts a key value pair formatted string and configures the conditions that would trigger a data stream to rollover when it has lifecycle configured.
forcedType: string
regex: ^max_age=(auto|[1-9][0-9]*[hd]),max_primary_shard_size=[1-9][0-9]*gb,min_docs=(0|[1-9][0-9]*),max_primary_shard_docs=[1-9][0-9]*$
regexFailureMessage: Must be in the format of "max_age=auto|<number><h|d>,max_primary_shard_size=<number>gb,min_docs=<number>,max_primary_shard_docs=<number>".
advanced: True
global: True
data_streams:
lifecycle:
poll_interval:
description: How often Elasticsearch checks what the next action is for all data streams with a built-in lifecycle.
forcedType: string
regex: "^[1-9][0-9]*[mhd]$"
regexFailureMessage: Must be a number followed by m, h, or d.
advanced: True
global: true
helpLink: elasticsearch
target:
merge:
policy:
merge_factor:
description: Data stream lifecycle implements tail merging by updating the Lucene merge policy factor for the target backing index. The merge factor is both the number of segments that should be merged together, and the maximum number of segments that we expect to find.
forcedType: int
regex: "^[1-9][0-9]*$"
advanced: True
global: true
helpLink: elasticsearch
floor_segment:
description: Data stream lifecycle implements tail merging by updating the Lucene merge policy floor segment for the target backing index. This floor segment size is a way to prevent indices from having a long tail of very small segments.
forcedType: string
regex: "^[1-9][0-9]*[MG]B$"
regexFailureMessage: Must be a number followed by MB or GB, such as 100MB.
advanced: True
global: true
helpLink: elasticsearch
action: action:
destructive_requires_name: destructive_requires_name:
description: Requires explicit index names when deleting indices. Prevents accidental deletion of indices via wildcard patterns. description: Requires explicit index names when deleting indices. Prevents accidental deletion of indices via wildcard patterns.
@@ -682,9 +645,6 @@ elasticsearch:
global: True global: True
advanced: True advanced: True
helpLink: elasticsearch helpLink: elasticsearch
so-assistant-chat: *dataStreamSettings
so-assistant-session: *dataStreamSettings
so-elastic-agent-monitor: *dataStreamSettings
so-logs-soc: *dataStreamSettings so-logs-soc: *dataStreamSettings
so-logs-system_x_auth: *dataStreamSettings so-logs-system_x_auth: *dataStreamSettings
so-logs-system_x_syslog: *dataStreamSettings so-logs-system_x_syslog: *dataStreamSettings
@@ -707,10 +667,7 @@ elasticsearch:
so-logs-elastic_agent_x_auditbeat: *dataStreamSettings so-logs-elastic_agent_x_auditbeat: *dataStreamSettings
so-logs-elastic_agent_x_cloudbeat: *dataStreamSettings so-logs-elastic_agent_x_cloudbeat: *dataStreamSettings
so-logs-elastic_agent_x_endpoint_security: *dataStreamSettings so-logs-elastic_agent_x_endpoint_security: *dataStreamSettings
so-logs-endpoint_x_actions: *dataStreamSettings
so-logs-endpoint_x_action_x_responses: *dataStreamSettings
so-logs-endpoint_x_alerts: *dataStreamSettings so-logs-endpoint_x_alerts: *dataStreamSettings
so-logs-endpoint_x_diagnostic_x_collection: *dataStreamSettings
so-logs-endpoint_x_events_x_api: *dataStreamSettings so-logs-endpoint_x_events_x_api: *dataStreamSettings
so-logs-endpoint_x_events_x_file: *dataStreamSettings so-logs-endpoint_x_events_x_file: *dataStreamSettings
so-logs-endpoint_x_events_x_library: *dataStreamSettings so-logs-endpoint_x_events_x_library: *dataStreamSettings
@@ -718,7 +675,6 @@ elasticsearch:
so-logs-endpoint_x_events_x_process: *dataStreamSettings so-logs-endpoint_x_events_x_process: *dataStreamSettings
so-logs-endpoint_x_events_x_registry: *dataStreamSettings so-logs-endpoint_x_events_x_registry: *dataStreamSettings
so-logs-endpoint_x_events_x_security: *dataStreamSettings so-logs-endpoint_x_events_x_security: *dataStreamSettings
so-logs-endpoint_x_heartbeat: *dataStreamSettings
so-logs-elastic_agent_x_filebeat: *dataStreamSettings so-logs-elastic_agent_x_filebeat: *dataStreamSettings
so-logs-elastic_agent_x_fleet_server: *dataStreamSettings so-logs-elastic_agent_x_fleet_server: *dataStreamSettings
so-logs-elastic_agent_x_heartbeat: *dataStreamSettings so-logs-elastic_agent_x_heartbeat: *dataStreamSettings
@@ -734,19 +690,6 @@ elasticsearch:
so-metrics-vsphere_x_datastore: *dataStreamSettings so-metrics-vsphere_x_datastore: *dataStreamSettings
so-metrics-vsphere_x_host: *dataStreamSettings so-metrics-vsphere_x_host: *dataStreamSettings
so-metrics-vsphere_x_virtualmachine: *dataStreamSettings so-metrics-vsphere_x_virtualmachine: *dataStreamSettings
so-metrics-system_x_core: *dataStreamSettings
so-metrics-system_x_cpu: *dataStreamSettings
so-metrics-system_x_diskio: *dataStreamSettings
so-metrics-system_x_filesystem: *dataStreamSettings
so-metrics-system_x_fsstat: *dataStreamSettings
so-metrics-system_x_load: *dataStreamSettings
so-metrics-system_x_memory: *dataStreamSettings
so-metrics-system_x_network: *dataStreamSettings
so-metrics-system_x_ntp: *dataStreamSettings
so-metrics-system_x_process: *dataStreamSettings
so-metrics-system_x_process_x_summary: *dataStreamSettings
so-metrics-system_x_socket_summary: *dataStreamSettings
so-metrics-system_x_uptime: *dataStreamSettings
so-common: *dataStreamSettings so-common: *dataStreamSettings
so-endgame: *dataStreamSettings so-endgame: *dataStreamSettings
so-idh: *dataStreamSettings so-idh: *dataStreamSettings
@@ -937,6 +880,17 @@ elasticsearch:
global: True global: True
advanced: True advanced: True
helpLink: elasticsearch helpLink: elasticsearch
rollover:
max_age:
description: Maximum age of index. Once an index reaches this limit, it will be rolled over into a new index.
global: True
advanced: True
helpLink: elasticsearch
max_primary_shard_size:
description: Maximum primary shard size. Once an index reaches this limit, it will be rolled over into a new index.
global: True
advanced: True
helpLink: elasticsearch
shrink: shrink:
method: method:
description: Shrink the index to a new index with fewer primary shards. Shrink operation is by count or size. description: Shrink the index to a new index with fewer primary shards. Shrink operation is by count or size.
@@ -1033,6 +987,8 @@ elasticsearch:
helpLink: elasticsearch helpLink: elasticsearch
sos-backup: *indexSettings sos-backup: *indexSettings
so-detection: *indexSettings so-detection: *indexSettings
so-assistant-chat: *indexSettings
so-assistant-session: *indexSettings
so-metrics-fleet_server_x_agent_status: &fleetMetricsSettings so-metrics-fleet_server_x_agent_status: &fleetMetricsSettings
index_sorting: index_sorting:
description: Sorts the index by event time, at the cost of additional processing resource consumption. description: Sorts the index by event time, at the cost of additional processing resource consumption.
+3 -9
View File
@@ -109,15 +109,9 @@
{% if not settings.get('index_sorting', False) | to_bool and settings.index_template.template.settings.index.sort is defined %} {% if not settings.get('index_sorting', False) | to_bool and settings.index_template.template.settings.index.sort is defined %}
{% do settings.index_template.template.settings.index.pop('sort') %} {% do settings.index_template.template.settings.index.pop('sort') %}
{% endif %} {% endif %}
{% if DATA_RETENTION_METHOD == 'DLM' and settings.index_template.data_stream is defined %} {% if DATA_RETENTION_METHOD == 'DLM' and settings.index_template.data_stream is defined and settings.data_stream_lifecycle is defined %}
{# Addon defaults are generated without data_stream_lifecycle, so fall back to global defaults. #} {% if settings.data_stream_lifecycle.data_retention is defined and settings.data_stream_lifecycle.data_retention %}
{% if settings.data_stream_lifecycle is defined %} {% do settings.index_template.template.update({'lifecycle': {'data_retention': settings.data_stream_lifecycle.data_retention}}) %}
{% set DATA_STREAM_LIFECYCLE = settings.data_stream_lifecycle %}
{% else %}
{% set DATA_STREAM_LIFECYCLE = DEFAULT_GLOBAL_OVERRIDES.data_stream_lifecycle %}
{% endif %}
{% if DATA_STREAM_LIFECYCLE.data_retention is defined and DATA_STREAM_LIFECYCLE.data_retention %}
{% do settings.index_template.template.update({'lifecycle': {'data_retention': DATA_STREAM_LIFECYCLE.data_retention}}) %}
{% else %} {% else %}
{% do settings.index_template.template.update({'lifecycle': {}}) %} {% do settings.index_template.template.update({'lifecycle': {}}) %}
{% endif %} {% endif %}
@@ -1,203 +0,0 @@
#!/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.
set -eo pipefail
SETTINGS='{"index":{"auto_expand_replicas":"0-1"}}'
KIBANA_PASSWORD=
INDEX_PATTERNS=(
'.entity_analytics.risk_score.lookup-*'
'.entity_analytics.watchlists.*'
'.entity_analytics.monitoring.users-*'
'.entity_analytics.entity-leads-*'
'.asset-criticality.asset-criticality-*'
'.workflows-executions'
'.workflows-step-executions'
'.entities.v2.latest.security_*'
'.entities.v2.history.security_*'
'risk-score.risk-score-latest-*'
'.metrics-endpoint.metadata_united_*'
'.metrics-endpoint.metadata_current_*'
)
DATA_STREAM_PATTERNS=(
'.entities.v2.updates.security_*'
'risk-score.risk-score-*'
'.rule-events'
'.alert-actions'
)
TEMPLATE_PATTERNS=(
'entities_v2_latest_security_default_index_template'
'entities_v2_history_security_default_index_template'
'.entities_v2_updates_security_default_index_template'
'.risk-score.risk-score-default-index-template'
'.rule-events'
'.alert-actions'
'.metrics-endpoint.metadata_united_default-template'
'.metrics-endpoint.metadata_current_default-template'
)
query_es() {
if so-elasticsearch-query "$@" --fail --retry 3 --retry-delay 5; then
return 0
fi
# retry failed attempts with so_kibana user (system managed indices reject so_elastic user)
local query_path="$1"
shift
if [[ -z "$KIBANA_PASSWORD" ]]; then
KIBANA_PASSWORD=$(salt-call pillar.get elasticsearch:auth:users:so_kibana_user:pass --out=newline_values_only)
fi
[[ -n "$KIBANA_PASSWORD" ]] || return 1
echo "Retrying ${query_path} as so_kibana." >&2
curl -K /opt/so/conf/elasticsearch/curl.config --user "so_kibana:${KIBANA_PASSWORD}" \
-s -k -L --fail --retry 3 --retry-delay 5 -H 'Content-Type: application/json' "https://localhost:9200/${query_path}" "$@"
}
# add auto_expand_replicas=0-1 to given index
set_auto_expand_replicas() {
local index="$1"
echo "Setting auto_expand_replicas to 0-1 on ${index}."
query_es "${index}/_settings" -XPUT -d "$SETTINGS" >/dev/null
}
# resolve index patterns and find each index with an unassigned replica
unassigned_replicas() {
local pattern="$1"
local resolved_indices response index
if ! resolved_indices=$(query_es "_resolve/index/${pattern}?expand_wildcards=all" 2>/dev/null); then
return 0
fi
while read -r index; do
if ! response=$(query_es "_cat/shards/${index}?format=json&h=index,prirep,state" 2>/dev/null); then
continue
fi
jq -r '.[]? | objects | select(.prirep == "r" and .state == "UNASSIGNED") | .index' <<<"$response"
done < <(jq -r '.indices[]?.name' <<<"$resolved_indices")
}
data_stream_indices() {
local pattern="$1"
local response
if ! response=$(query_es "_data_stream/${pattern}?expand_wildcards=all" 2>/dev/null); then
return 0
fi
jq -r '.data_streams[]?.indices[]?.index_name' <<<"$response"
}
update_system_indices() {
local pattern="$1"
local index
while read -r index; do
[[ -n "$index" ]] && set_auto_expand_replicas "$index"
done < <(unassigned_replicas "$pattern")
}
# update data stream backing indices with unassigned replicas
update_system_ds() {
local pattern="$1"
local index
while read -r index; do
while read -r unassigned_index; do
[[ -n "$unassigned_index" ]] && set_auto_expand_replicas "$unassigned_index"
done < <(unassigned_replicas "$index")
done < <(data_stream_indices "$pattern")
}
has_unassigned_replicas() {
local pattern="$1"
local index
index=$(unassigned_replicas "$pattern" | sed -n '1p')
[[ -n "$index" ]]
}
data_stream_has_unassigned_replicas() {
local pattern="$1"
local index
while read -r index; do
has_unassigned_replicas "$index" && return 0
done < <(data_stream_indices "$pattern")
return 1
}
needs_patch() {
local pattern
for pattern in "${INDEX_PATTERNS[@]}"; do
has_unassigned_replicas "$pattern" && return 0
done
for pattern in "${DATA_STREAM_PATTERNS[@]}"; do
data_stream_has_unassigned_replicas "$pattern" && return 0
done
return 1
}
# get index templates, update with auto_expand_replicas=0-1, and PUT back. Keeping mappings/settings/aliases in-place
update_system_templates() {
local pattern="$1"
local templates name response template auto_expand_replicas
if ! templates=$(query_es "_index_template/${pattern}" 2>/dev/null); then
return 0
fi
while read -r name; do
response=$(query_es "_index_template/${name}")
template=$(jq -c '.index_templates[0].index_template' <<<"$response")
auto_expand_replicas=$(jq -r '.template.settings["index.auto_expand_replicas"] // .template.settings.index.auto_expand_replicas // empty' <<<"$template")
[[ "$auto_expand_replicas" == "0-1" ]] && continue
template=$(jq '
if (.template.settings.index | type) == "object" then
.template.settings.index.auto_expand_replicas = "0-1"
else
.template.settings["index.auto_expand_replicas"] = "0-1"
end
| del(.created_date_millis, .modified_date_millis)
' <<<"$template")
echo "Setting auto_expand_replicas to 0-1 on index template ${name}."
query_es "_index_template/${name}" -XPUT -d "$template" >/dev/null
done < <(jq -r '.index_templates[]?.name' <<<"$templates")
}
if [[ "${1:-}" == "--check" ]]; then
needs_patch
exit $?
fi
if [[ $# -ne 0 ]]; then
echo "Usage: $0 [--check]" >&2
exit 1
fi
patched=false
for pattern in "${INDEX_PATTERNS[@]}"; do
if has_unassigned_replicas "$pattern"; then
update_system_indices "$pattern"
patched=true
fi
done
for pattern in "${DATA_STREAM_PATTERNS[@]}"; do
if data_stream_has_unassigned_replicas "$pattern"; then
update_system_ds "$pattern"
patched=true
fi
done
if [[ "$patched" == true ]]; then
for pattern in "${TEMPLATE_PATTERNS[@]}"; do
update_system_templates "$pattern"
done
fi
+14 -33
View File
@@ -4,19 +4,11 @@
{%- set role = GLOBALS.role.split('-')[1] %} {%- set role = GLOBALS.role.split('-')[1] %}
{%- from 'firewall/containers.map.jinja' import NODE_CONTAINERS %} {%- from 'firewall/containers.map.jinja' import NODE_CONTAINERS %}
{%- set NODE_NETWORKS = [] %}
{%- for NETNAME, NETWORK in DOCKERMERGED.networks.items() %}
{%- if not NETWORK.get('manager_only') or GLOBALS.get('is_manager', False) %}
{%- do NODE_NETWORKS.append(NETNAME) %}
{%- endif %}
{%- endfor %}
{%- set PR = [] %} {%- set PR = [] %}
{%- set D1 = [] %} {%- set D1 = [] %}
{%- set D2 = [] %} {%- set D2 = [] %}
{%- for container in NODE_CONTAINERS %} {%- for container in NODE_CONTAINERS %}
{%- set IP = DOCKERMERGED.containers[container].ip %} {%- set IP = DOCKERMERGED.containers[container].ip %}
{%- set BRIDGE = DOCKERMERGED.containers[container].network %}
{%- if DOCKERMERGED.containers[container].port_bindings is defined %} {%- if DOCKERMERGED.containers[container].port_bindings is defined %}
{%- for binding in DOCKERMERGED.containers[container].port_bindings %} {%- for binding in DOCKERMERGED.containers[container].port_bindings %}
{#- cant split int so we convert to string #} {#- cant split int so we convert to string #}
@@ -43,11 +35,11 @@
{%- endif %} {%- endif %}
{%- do PR.append("-A POSTROUTING -s " ~ DOCKERMERGED.containers[container].ip ~ "/32 -d " ~ DOCKERMERGED.containers[container].ip ~ "/32 -p " ~ proto ~ " -m " ~ proto ~ " --dport " ~ containerPort ~ " -j MASQUERADE") %} {%- do PR.append("-A POSTROUTING -s " ~ DOCKERMERGED.containers[container].ip ~ "/32 -d " ~ DOCKERMERGED.containers[container].ip ~ "/32 -p " ~ proto ~ " -m " ~ proto ~ " --dport " ~ containerPort ~ " -j MASQUERADE") %}
{%- if bindip | length and bindip != '0.0.0.0' %} {%- if bindip | length and bindip != '0.0.0.0' %}
{%- do D1.append("-A DOCKER -d " ~ bindip ~ "/32 ! -i " ~ BRIDGE ~ " -p " ~ proto ~ " -m " ~ proto ~ " --dport " ~ hostPort ~ " -j DNAT --to-destination " ~ DOCKERMERGED.containers[container].ip ~ ":" ~ containerPort) %} {%- do D1.append("-A DOCKER -d " ~ bindip ~ "/32 ! -i sobridge -p " ~ proto ~ " -m " ~ proto ~ " --dport " ~ hostPort ~ " -j DNAT --to-destination " ~ DOCKERMERGED.containers[container].ip ~ ":" ~ containerPort) %}
{%- else %} {%- else %}
{%- do D1.append("-A DOCKER ! -i " ~ BRIDGE ~ " -p " ~ proto ~ " -m " ~ proto ~ " --dport " ~ hostPort ~ " -j DNAT --to-destination " ~ DOCKERMERGED.containers[container].ip ~ ":" ~ containerPort) %} {%- do D1.append("-A DOCKER ! -i sobridge -p " ~ proto ~ " -m " ~ proto ~ " --dport " ~ hostPort ~ " -j DNAT --to-destination " ~ DOCKERMERGED.containers[container].ip ~ ":" ~ containerPort) %}
{%- endif %} {%- endif %}
{%- do D2.append("-A DOCKER -d " ~ DOCKERMERGED.containers[container].ip ~ "/32 ! -i " ~ BRIDGE ~ " -o " ~ BRIDGE ~ " -p " ~ proto ~ " -m " ~ proto ~ " --dport " ~ containerPort ~ " -j ACCEPT") %} {%- do D2.append("-A DOCKER -d " ~ DOCKERMERGED.containers[container].ip ~ "/32 ! -i sobridge -o sobridge -p " ~ proto ~ " -m " ~ proto ~ " --dport " ~ containerPort ~ " -j ACCEPT") %}
{%- endfor %} {%- endfor %}
{%- endif %} {%- endif %}
{%- endfor %} {%- endfor %}
@@ -60,15 +52,11 @@
:DOCKER - [0:0] :DOCKER - [0:0]
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER -A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
{%- for NETNAME in NODE_NETWORKS %} -A POSTROUTING -s {{DOCKERMERGED.range}} ! -o sobridge -j MASQUERADE
-A POSTROUTING -s {{ DOCKERMERGED.networks[NETNAME].range }} ! -o {{ NETNAME }} -j MASQUERADE
{%- endfor %}
{%- for rule in PR %} {%- for rule in PR %}
{{ rule }} {{ rule }}
{%- endfor %} {%- endfor %}
{%- for NETNAME in NODE_NETWORKS %} -A DOCKER -i sobridge -j RETURN
-A DOCKER -i {{ NETNAME }} -j RETURN
{%- endfor %}
{%- for rule in D1 %} {%- for rule in D1 %}
{{ rule }} {{ rule }}
{%- endfor %} {%- endfor %}
@@ -109,12 +97,10 @@ COMMIT
{%- endif %} {%- endif %}
-A FORWARD -j DOCKER-USER -A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1 -A FORWARD -j DOCKER-ISOLATION-STAGE-1
{%- for NETNAME in NODE_NETWORKS %} -A FORWARD -o sobridge -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o {{ NETNAME }} -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A FORWARD -o sobridge -j DOCKER
-A FORWARD -o {{ NETNAME }} -j DOCKER -A FORWARD -i sobridge ! -o sobridge -j ACCEPT
-A FORWARD -i {{ NETNAME }} ! -o {{ NETNAME }} -j ACCEPT -A FORWARD -i sobridge -o sobridge -j ACCEPT
-A FORWARD -i {{ NETNAME }} -o {{ NETNAME }} -j ACCEPT
{%- endfor %}
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i lo -j ACCEPT -A FORWARD -i lo -j ACCEPT
-A FORWARD -m conntrack --ctstate INVALID -j DROP -A FORWARD -m conntrack --ctstate INVALID -j DROP
@@ -126,18 +112,13 @@ COMMIT
{%- for rule in D2 %} {%- for rule in D2 %}
{{ rule }} {{ rule }}
{%- endfor %} {%- endfor %}
{% for NETNAME in NODE_NETWORKS %}
-A DOCKER-ISOLATION-STAGE-1 -i {{ NETNAME }} ! -o {{ NETNAME }} -j DOCKER-ISOLATION-STAGE-2 -A DOCKER-ISOLATION-STAGE-1 -i sobridge ! -o sobridge -j DOCKER-ISOLATION-STAGE-2
{%- endfor %}
-A DOCKER-ISOLATION-STAGE-1 -j RETURN -A DOCKER-ISOLATION-STAGE-1 -j RETURN
{%- for NETNAME in NODE_NETWORKS %} -A DOCKER-ISOLATION-STAGE-2 -o sobridge -j DROP
-A DOCKER-ISOLATION-STAGE-2 -o {{ NETNAME }} -j DROP
{%- endfor %}
-A DOCKER-ISOLATION-STAGE-2 -j RETURN -A DOCKER-ISOLATION-STAGE-2 -j RETURN
{%- for NETNAME in NODE_NETWORKS %} -A DOCKER-USER ! -i sobridge -o sobridge -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A DOCKER-USER ! -i {{ NETNAME }} -o {{ NETNAME }} -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -A DOCKER-USER ! -i sobridge -o sobridge -j LOGGING
-A DOCKER-USER ! -i {{ NETNAME }} -o {{ NETNAME }} -j LOGGING
{%- endfor %}
-A DOCKER-USER -j RETURN -A DOCKER-USER -j RETURN
-A LOGGING -m limit --limit 2/min -j LOG --log-prefix "IPTables-dropped: " -A LOGGING -m limit --limit 2/min -j LOG --log-prefix "IPTables-dropped: "
-A LOGGING -j DROP -A LOGGING -j DROP
+2 -6
View File
@@ -4,12 +4,8 @@
{# add our ip to self #} {# add our ip to self #}
{% do FIREWALL_DEFAULT.firewall.hostgroups.self.append(GLOBALS.node_ip) %} {% do FIREWALL_DEFAULT.firewall.hostgroups.self.append(GLOBALS.node_ip) %}
{# add dockernet ranges #} {# add dockernet range #}
{% for NETNAME, NETWORK in DOCKERMERGED.networks.items() %} {% do FIREWALL_DEFAULT.firewall.hostgroups.dockernet.append(DOCKERMERGED.range) %}
{% if not NETWORK.get('manager_only') or GLOBALS.get('is_manager', False) %}
{% do FIREWALL_DEFAULT.firewall.hostgroups.dockernet.append(NETWORK.range) %}
{% endif %}
{% endfor %}
{% if GLOBALS.role == 'so-idh' %} {% if GLOBALS.role == 'so-idh' %}
{% from 'idh/opencanary_config.map.jinja' import IDH_PORTGROUPS %} {% from 'idh/opencanary_config.map.jinja' import IDH_PORTGROUPS %}
+3 -3
View File
@@ -26,8 +26,8 @@ so-hydra:
- hostname: hydra - hostname: hydra
- name: so-hydra - name: so-hydra
- networks: - networks:
- soauth: - sobridge:
- ipv4_address: {{ DOCKERMERGED.containers['so-hydra'].ips['soauth'] }} - ipv4_address: {{ DOCKERMERGED.containers['so-hydra'].ip }}
- binds: - binds:
- /opt/so/conf/hydra/:/hydra-conf:ro - /opt/so/conf/hydra/:/hydra-conf:ro
- /opt/so/log/hydra/:/hydra-log:rw - /opt/so/log/hydra/:/hydra-log:rw
@@ -73,7 +73,7 @@ delete_so-hydra_so-status.disabled:
wait_for_hydra: wait_for_hydra:
http.wait_for_successful_query: http.wait_for_successful_query:
- name: 'http://{{ DOCKERMERGED.containers['so-hydra'].ips['soauth'] }}:4444/health/alive' - name: 'http://{{ GLOBALS.manager }}:4444/health/alive'
- ssl: True - ssl: True
- verify_ssl: False - verify_ssl: False
- status: - status:
-4
View File
@@ -21,16 +21,12 @@ hypervisor_sbin:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://hypervisor/tools/sbin - source: salt://hypervisor/tools/sbin
- user: root
- group: root
- file_mode: 744 - file_mode: 744
hypervisor_sbin_jinja: hypervisor_sbin_jinja:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://hypervisor/tools/sbin_jinja - source: salt://hypervisor/tools/sbin_jinja
- user: root
- group: root
- template: jinja - template: jinja
- file_mode: 744 - file_mode: 744
+2 -2
View File
@@ -86,8 +86,8 @@ idh_sbin:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://idh/tools/sbin - source: salt://idh/tools/sbin
- user: root - user: 939
- group: root - group: 939
- file_mode: 755 - file_mode: 755
#idh_sbin_jinja: #idh_sbin_jinja:
+2 -2
View File
@@ -41,8 +41,8 @@ influxdb_sbin:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://influxdb/tools/sbin - source: salt://influxdb/tools/sbin
- user: root - user: 939
- group: root - group: 939
- file_mode: 755 - file_mode: 755
#influxdb_sbin_jinja: #influxdb_sbin_jinja:
+1 -3
View File
@@ -94,11 +94,9 @@ metrics_link_file:
- docker_container: so-influxdb - docker_container: so-influxdb
# Install cron job to determine size of influxdb for telegraf # Install cron job to determine size of influxdb for telegraf
# telegraf reads this while the cron rewrites it, so write aside and rename rather than
# truncating in place. tgraflogdir recurses ownership, so the temp file is chowned to match
get_influxdb_size: get_influxdb_size:
cron.present: cron.present:
- name: 'du -s -k /nsm/influxdb | cut -f1 > /opt/so/log/telegraf/influxdb_size.log.tmp 2>&1; chown 939:939 /opt/so/log/telegraf/influxdb_size.log.tmp; mv -f /opt/so/log/telegraf/influxdb_size.log.tmp /opt/so/log/telegraf/influxdb_size.log' - name: 'du -s -k /nsm/influxdb | cut -f1 > /opt/so/log/telegraf/influxdb_size.log 2>&1'
- identifier: get_influxdb_size - identifier: get_influxdb_size
- user: root - user: root
- minute: '*/1' - minute: '*/1'
+4 -5
View File
@@ -21,7 +21,6 @@ kafka_user:
- gid: 960 - gid: 960
- home: /opt/so/conf/kafka - home: /opt/so/conf/kafka
- createhome: False - createhome: False
- shell: /sbin/nologin
kafka_home_dir: kafka_home_dir:
file.absent: file.absent:
@@ -31,16 +30,16 @@ kafka_sbin_tools:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://kafka/tools/sbin - source: salt://kafka/tools/sbin
- user: root - user: 960
- group: root - group: 960
- file_mode: 755 - file_mode: 755
kafka_sbin_jinja_tools: kafka_sbin_jinja_tools:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://kafka/tools/sbin_jinja - source: salt://kafka/tools/sbin_jinja
- user: root - user: 960
- group: root - group: 960
- file_mode: 755 - file_mode: 755
- template: jinja - template: jinja
- defaults: - defaults:
+4 -5
View File
@@ -22,7 +22,6 @@ kibana:
- gid: 932 - gid: 932
- home: /opt/so/conf/kibana - home: /opt/so/conf/kibana
- createhome: False - createhome: False
- shell: /sbin/nologin
# Drop the correct nginx config based on role # Drop the correct nginx config based on role
@@ -37,16 +36,16 @@ kibana_sbin:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://kibana/tools/sbin - source: salt://kibana/tools/sbin
- user: root - user: 932
- group: root - group: 939
- file_mode: 755 - file_mode: 755
kibana_sbin_jinja: kibana_sbin_jinja:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://kibana/tools/sbin_jinja - source: salt://kibana/tools/sbin_jinja
- user: root - user: 932
- group: root - group: 939
- file_mode: 755 - file_mode: 755
- template: jinja - template: jinja
- defaults: - defaults:
+1 -1
View File
@@ -22,7 +22,7 @@ kibana:
- default - default
- file - file
migrations: migrations:
discardCorruptObjects: "9.4.5" discardCorruptObjects: "9.3.7"
telemetry: telemetry:
enabled: False enabled: False
xpack: xpack:
+1 -1
View File
@@ -1,5 +1,5 @@
{"attributes":{"fieldFormatMap":"{\"match_body.source_ip\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:'\\\"{{value}}\\\"')),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"match_body.destination_ip\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:'\\\"{{value}}\\\"')),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}}}","fields":"[{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":1,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"alert_info.slack_username_override\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"alert_info.slack_username_override.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"alert_info.slack_webhook_url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"alert_info.slack_webhook_url.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"alert_info.type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"alert_info.type.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"alert_sent\",\"type\":\"boolean\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"alert_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"data.rule\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"data.rule.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"endtime\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"exponent\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hits\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"match_body.@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"match_body.@version\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"match_body.@version.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"match_body.signature_info\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"match_body.signature_info.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"match_body._id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"match_body._id.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"match_body._index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\Line truncated {"attributes":{"fieldFormatMap":"{\"match_body.source_ip\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:'\\\"{{value}}\\\"')),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"match_body.destination_ip\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:'\\\"{{value}}\\\"')),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}}}","fields":"[{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":1,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"alert_info.slack_username_override\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"alert_info.slack_username_override.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"alert_info.slack_webhook_url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"alert_info.slack_webhook_url.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"alert_info.type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"alert_info.type.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"alert_sent\",\"type\":\"boolean\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"alert_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"data.rule\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"data.rule.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"endtime\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"exponent\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hits\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"match_body.@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"match_body.@version\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"match_body.@version.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"match_body.signature_info\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"match_body.signature_info.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"match_body._id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"match_body._id.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"match_body._index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\Line truncated
{"attributes":{"fieldFormatMap":"{\"_id\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"https://PLACEHOLDER/#/hunt?q=_id%3A%22{{value}}%22\",\"labelTemplate\":\"Hunt and optionally pivot to PCAP/Cases\"}},\"uid\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:{{value}})),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"source_ip\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:'\\\"{{value}}\\\"')),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"destination_ip\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:'\\\"{{value}}\\\"')),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"source_port\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:'source_port:\\\"{{value}}\\\" OR destination_port:\\\"{{value}}\\\"')),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"destination_port\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:'source_port:\\\"{{value}}\\\" OR destination_port:\\\"{{value}}\\\"')),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"fuid\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:{{value}})),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"resp_fuids\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:{{value}})),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"orig_fuids\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:{{value}})),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"sid\":{\"id\":\"number\",\"params\":{\"pattern\":\"0\"}},\"port\":{\"id\":\"number\",\"params\":{\"pattern\":\"0.[000]\"}},\"query\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:{{value}})),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"query.keyword\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:{{value}})),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"server_name\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:{{value}})),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"server_name.keyword\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:OLine truncated {"attributes":{"fieldFormatMap":"{\"_id\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"https://PLACEHOLDER/#/hunt?q=_id%3A%22{{value}}%22\",\"labelTemplate\":\"Hunt and optionally pivot to PCAP/Cases\"}},\"uid\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:{{value}})),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"source_ip\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:'\\\"{{value}}\\\"')),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"destination_ip\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:'\\\"{{value}}\\\"')),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"source_port\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:'source_port:\\\"{{value}}\\\" OR destination_port:\\\"{{value}}\\\"')),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"destination_port\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:'source_port:\\\"{{value}}\\\" OR destination_port:\\\"{{value}}\\\"')),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"fuid\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:{{value}})),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"resp_fuids\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:{{value}})),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"orig_fuids\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:{{value}})),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"sid\":{\"id\":\"number\",\"params\":{\"pattern\":\"0\"}},\"port\":{\"id\":\"number\",\"params\":{\"pattern\":\"0.[000]\"}},\"query\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:{{value}})),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"query.keyword\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:{{value}})),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"server_name\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:Off,pause:!f,value:0),time:(from:now-24h,mode:quick,to:now))&_a=(columns:!(_source),index:'*:logstash-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:{{value}})),sort:!('@timestamp',desc))\",\"labelTemplate\":\"{{value}}\"}},\"server_name.keyword\":{\"id\":\"url\",\"params\":{\"urlTemplate\":\"/kibana/app/dashboards#/view/68563ed0-34bf-11e7-9b32-bb903919ead9?_g=(refreshInterval:(display:OLine truncated
{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"title":"Security Onion - Network Data","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Security Onion - Network Data\",\"type\":\"markdown\",\"aggs\":[],\"params\":{\"fontSize\":12,\"openLinksInNewTab\":false,\"markdown\":\"[Home](/kibana/app/dashboards#/view/a8411b30-6d03-11ea-b301-3d6c35840645) \\n \\n**Datasets** \\n[Connections](/kibana/app/dashboards#/view/0cc628b0-6e9f-11ea-9266-1fd14ca6af34) | [DCE/RPC](/kibana/app/dashboards#/view/9e882df0-72c5-11ea-8dd2-9d8795a1200b) |\\n[DHCP](/kibana/app/dashboards#/view/80625c10-96dd-11ea-814e-bb515e873c2c) \\n[DNP3](/kibana/app/dashboards#/view/b1f52180-755a-11ea-9565-7315f4ee5cac) | [DNS](/kibana/app/dashboards#/view/55ac6bf0-6ec4-11ea-9266-1fd14ca6af34) |\\n[FTP](/kibana/app/dashboards#/view/739bfad0-755a-11ea-9565-7315f4ee5cac) |\\n[HTTP](/kibana/app/dashboards#/view/44e9c820-6eb1-11ea-9266-1fd14ca6af34) | [Intel](/kibana/app/dashboards#/view/85b529a0-0e5a-11eb-a255-e1e8e85e3571) | [IRC](/kibana/app/dashboards#/view/38523560-75ba-11ea-9565-7315f4ee5cac) |\\n[Kerberos](/kibana/app/dashboards#/view/b207ab90-75bc-11ea-9565-7315f4ee5cac) \\n[Modbus](/kibana/app/dashboards#/view/886a7b90-75bd-11ea-9565-7315f4ee5cac) | \\n[MySQL](/kibana/app/dashboards#/view/c3ced6d0-75be-11ea-9565-7315f4ee5cac) | \\n[NTLM](/kibana/app/dashboards#/view/558292e0-75c1-11ea-9565-7315f4ee5cac) | \\n[PE](/kibana/app/dashboards#/view/94b55b90-c761-11ea-bebb-37c5ab5894ea) |\\n[RADIUS](/kibana/app/dashboards#/view/b9769e60-75c4-11ea-9565-7315f4ee5cac) | [RDP](/kibana/app/dashboards#/view/5b743150-75c5-11ea-9565-7315f4ee5cac) | \\n[RFB](/kibana/app/dashboards#/view/c8b3c360-75c6-11ea-9565-7315f4ee5cac) | [SIP](/kibana/app/dashboards#/view/dd98e260-75c6-11ea-9565-7315f4ee5cac) \\n[SMB](/kibana/app/dashboards#/view/f24d7b80-75c6-11ea-9565-7315f4ee5cac) | [SMTP](/kibana/app/dashboards#/view/00304500-75e7-11ea-9565-7315f4ee5cac) | [SNMP](/kibana/app/dashboards#/view/96522610-75e8-11ea-9565-7315f4ee5cac) | \\n[SSH](/kibana/app/dashboards#/view/9dfd77e0-75eb-11ea-9565-7315f4ee5cac) | [SSL](/kibana/app/dashboards#/view/efae8de0-75eb-11ea-9565-7315f4ee5cac) | [Syslog](/kibana/app/dashboards#/view/66499a20-75ed-11ea-9565-7315f4ee5cac) | [Tunnels](/kibana/app/dashboards#/view/c962dd60-75ed-11ea-9565-7315f4ee5cac) | [X.509](/kibana/app/dashboards#/view/2e0865f0-75ee-11ea-9565-7315f4ee5cac) \\n\"}}"},"coreMigrationVersion":"8.7.1","created_at":"2023-07-20T15:26:57.180Z","id":"7f822930-6ea4-11ea-9266-1fd14ca6af34","migrationVersion":{"visualization":"8.5.0"},"references":[],"sort":[1689866817180,4251],"type":"visualization","updated_at":"2023-07-20T15:26:57.180Z","version":"WzQwNzgsMV0="} {"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"title":"Security Onion - Network Data","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Security Onion - Network Data\",\"type\":\"markdown\",\"aggs\":[],\"params\":{\"fontSize\":12,\"openLinksInNewTab\":false,\"markdown\":\"[Home](/kibana/app/dashboards#/view/a8411b30-6d03-11ea-b301-3d6c35840645) \\n \\n**Datasets** \\n[Connections](/kibana/app/dashboards#/view/0cc628b0-6e9f-11ea-9266-1fd14ca6af34) | [DCE/RPC](/kibana/app/dashboards#/view/9e882df0-72c5-11ea-8dd2-9d8795a1200b) |\\n[DHCP](/kibana/app/dashboards#/view/80625c10-96dd-11ea-814e-bb515e873c2c) \\n[DNP3](/kibana/app/dashboards#/view/b1f52180-755a-11ea-9565-7315f4ee5cac) | [DNS](/kibana/app/dashboards#/view/55ac6bf0-6ec4-11ea-9266-1fd14ca6af34) |\\n[FTP](/kibana/app/dashboards#/view/739bfad0-755a-11ea-9565-7315f4ee5cac) |\\n[HTTP](/kibana/app/dashboards#/view/44e9c820-6eb1-11ea-9266-1fd14ca6af34) | [Intel](/kibana/app/dashboards#/view/85b529a0-0e5a-11eb-a255-e1e8e85e3571) | [IRC](/kibana/app/dashboards#/view/38523560-75ba-11ea-9565-7315f4ee5cac) |\\n[Kerberos](/kibana/app/dashboards#/view/b207ab90-75bc-11ea-9565-7315f4ee5cac) \\n[Modbus](/kibana/app/dashboards#/view/886a7b90-75bd-11ea-9565-7315f4ee5cac) | \\n[MySQL](/kibana/app/dashboards#/view/c3ced6d0-75be-11ea-9565-7315f4ee5cac) | \\n[NTLM](/kibana/app/dashboards#/view/558292e0-75c1-11ea-9565-7315f4ee5cac) | \\n[PE](/kibana/app/dashboards#/view/94b55b90-c761-11ea-bebb-37c5ab5894ea) |\\n[RADIUS](/kibana/app/dashboards#/view/b9769e60-75c4-11ea-9565-7315f4ee5cac) | [RDP](/kibana/app/dashboards#/view/5b743150-75c5-11ea-9565-7315f4ee5cac) | \\n[RFB](/kibana/app/dashboards#/view/c8b3c360-75c6-11ea-9565-7315f4ee5cac) | [SIP](/kibana/app/dashboards#/view/dd98e260-75c6-11ea-9565-7315f4ee5cac) \\n[SMB](/kibana/app/dashboards#/view/f24d7b80-75c6-11ea-9565-7315f4ee5cac) | [SMTP](/kibana/app/dashboards#/view/00304500-75e7-11ea-9565-7315f4ee5cac) | [SNMP](/kibana/app/dashboards#/view/96522610-75e8-11ea-9565-7315f4ee5cac) | \\n[SSH](/kibana/app/dashboards#/view/9dfd77e0-75eb-11ea-9565-7315f4ee5cac) | [SSL](/kibana/app/dashboards#/view/efae8de0-75eb-11ea-9565-7315f4ee5cac) | [Syslog](/kibana/app/dashboards#/view/66499a20-75ed-11ea-9565-7315f4ee5cac) | [Tunnels](/kibana/app/dashboards#/view/c962dd60-75ed-11ea-9565-7315f4ee5cac) | [X.509](/kibana/app/dashboards#/view/2e0865f0-75ee-11ea-9565-7315f4ee5cac) \\n\"}}"},"coreMigrationVersion":"8.7.1","created_at":"2023-07-20T15:26:57.180Z","id":"7f822930-6ea4-11ea-9266-1fd14ca6af34","migrationVersion":{"visualization":"8.5.0"},"references":[],"sort":[1689866817180,4251],"type":"visualization","updated_at":"2023-07-20T15:26:57.180Z","version":"WzQwNzgsMV0="}
{"attributes":{"allowNoIndex":true,"fieldFormatMap":"{\"Ransomware.child_processes.pid\":{\"id\":\"string\"},\"Ransomware.pid\":{\"id\":\"string\"},\"Responses.@timestamp\":{\"id\":\"string\"},\"Target.process.parent.pgid\":{\"id\":\"string\"},\"Target.process.parent.pid\":{\"id\":\"string\"},\"Target.process.parent.ppid\":{\"id\":\"string\"},\"Target.process.parent.thread.id\":{\"id\":\"string\"},\"Target.process.pgid\":{\"id\":\"string\"},\"Target.process.pid\":{\"id\":\"string\"},\"Target.process.ppid\":{\"id\":\"string\"},\"Target.process.thread.id\":{\"id\":\"string\"},\"event.sequence\":{\"id\":\"string\"},\"event.severity\":{\"id\":\"string\"},\"process.parent.pgid\":{\"id\":\"string\"},\"process.parent.pid\":{\"id\":\"string\"},\"process.parent.ppid\":{\"id\":\"string\"},\"process.parent.thread.id\":{\"id\":\"string\"},\"process.pgid\":{\"id\":\"string\"},\"process.pid\":{\"id\":\"string\"},\"process.ppid\":{\"id\":\"string\"},\"process.thread.id\":{\"id\":\"string\"},\"threat.enrichments.indicator.file.elf.header.entrypoint\":{\"id\":\"string\"},\"threat.enrichments.indicator.file.elf.sections.chi2\":{\"id\":\"number\"},\"threat.enrichments.indicator.file.elf.sections.entropy\":{\"id\":\"number\"},\"threat.enrichments.indicator.file.elf.sections.physical_size\":{\"id\":\"bytes\"},\"threat.enrichments.indicator.file.elf.sections.virtual_address\":{\"id\":\"string\"},\"threat.enrichments.indicator.file.elf.sections.virtual_size\":{\"id\":\"string\"},\"threat.enrichments.indicator.url.port\":{\"id\":\"string\"},\"threat.indicator.file.elf.header.entrypoint\":{\"id\":\"string\"},\"threat.indicator.file.elf.sections.chi2\":{\"id\":\"number\"},\"threat.indicator.file.elf.sections.entropy\":{\"id\":\"number\"},\"threat.indicator.file.elf.sections.physical_size\":{\"id\":\"bytes\"},\"threat.indicator.file.elf.sections.virtual_address\":{\"id\":\"string\"},\"threat.indicator.file.elf.sections.virtual_size\":{\"id\":\"string\"},\"threat.indicator.url.port\":{\"id\":\"string\"},\"destination.bytes\":{\"id\":\"bytes\"},\"destination.port\":{\"id\":\"string\"},\"http.request.body.bytes\":{\"id\":\"bytes\"},\"http.request.bytes\":{\"id\":\"bytes\"},\"http.response.body.bytes\":{\"id\":\"bytes\"},\"http.response.bytes\":{\"id\":\"bytes\"},\"http.response.status_code\":{\"id\":\"string\"},\"network.bytes\":{\"id\":\"bytes\"},\"source.bytes\":{\"id\":\"bytes\"},\"source.port\":{\"id\":\"string\"}}","fields":"[{\"name\":\"cloud.account.id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.availability_zone\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.instance.id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.instance.name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.machine.type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.provider\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.region\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.project.id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.image.id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.image.name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.labels\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.architecture\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.domain\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.hosLine truncated {"attributes":{"allowNoIndex":true,"fieldFormatMap":"{\"Ransomware.child_processes.pid\":{\"id\":\"string\"},\"Ransomware.pid\":{\"id\":\"string\"},\"Responses.@timestamp\":{\"id\":\"string\"},\"Target.process.parent.pgid\":{\"id\":\"string\"},\"Target.process.parent.pid\":{\"id\":\"string\"},\"Target.process.parent.ppid\":{\"id\":\"string\"},\"Target.process.parent.thread.id\":{\"id\":\"string\"},\"Target.process.pgid\":{\"id\":\"string\"},\"Target.process.pid\":{\"id\":\"string\"},\"Target.process.ppid\":{\"id\":\"string\"},\"Target.process.thread.id\":{\"id\":\"string\"},\"event.sequence\":{\"id\":\"string\"},\"event.severity\":{\"id\":\"string\"},\"process.parent.pgid\":{\"id\":\"string\"},\"process.parent.pid\":{\"id\":\"string\"},\"process.parent.ppid\":{\"id\":\"string\"},\"process.parent.thread.id\":{\"id\":\"string\"},\"process.pgid\":{\"id\":\"string\"},\"process.pid\":{\"id\":\"string\"},\"process.ppid\":{\"id\":\"string\"},\"process.thread.id\":{\"id\":\"string\"},\"threat.enrichments.indicator.file.elf.header.entrypoint\":{\"id\":\"string\"},\"threat.enrichments.indicator.file.elf.sections.chi2\":{\"id\":\"number\"},\"threat.enrichments.indicator.file.elf.sections.entropy\":{\"id\":\"number\"},\"threat.enrichments.indicator.file.elf.sections.physical_size\":{\"id\":\"bytes\"},\"threat.enrichments.indicator.file.elf.sections.virtual_address\":{\"id\":\"string\"},\"threat.enrichments.indicator.file.elf.sections.virtual_size\":{\"id\":\"string\"},\"threat.enrichments.indicator.url.port\":{\"id\":\"string\"},\"threat.indicator.file.elf.header.entrypoint\":{\"id\":\"string\"},\"threat.indicator.file.elf.sections.chi2\":{\"id\":\"number\"},\"threat.indicator.file.elf.sections.entropy\":{\"id\":\"number\"},\"threat.indicator.file.elf.sections.physical_size\":{\"id\":\"bytes\"},\"threat.indicator.file.elf.sections.virtual_address\":{\"id\":\"string\"},\"threat.indicator.file.elf.sections.virtual_size\":{\"id\":\"string\"},\"threat.indicator.url.port\":{\"id\":\"string\"},\"destination.bytes\":{\"id\":\"bytes\"},\"destination.port\":{\"id\":\"string\"},\"http.request.body.bytes\":{\"id\":\"bytes\"},\"http.request.bytes\":{\"id\":\"bytes\"},\"http.response.body.bytes\":{\"id\":\"bytes\"},\"http.response.bytes\":{\"id\":\"bytes\"},\"http.response.status_code\":{\"id\":\"string\"},\"network.bytes\":{\"id\":\"bytes\"},\"source.bytes\":{\"id\":\"bytes\"},\"source.port\":{\"id\":\"string\"}}","fields":"[{\"name\":\"cloud.account.id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.availability_zone\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.instance.id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.instance.name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.machine.type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.provider\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.region\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.project.id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"cloud.image.id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.image.name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.labels\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"container.name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.architecture\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.domain\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host.hosLine truncated
{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Security Onion - All Logs","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Security Onion - All Logs\",\"type\":\"metric\",\"params\":{\"metric\":{\"percentageMode\":false,\"useRanges\":false,\"colorSchema\":\"Green to Red\",\"metricColorMode\":\"None\",\"colorsRange\":[{\"type\":\"range\",\"from\":0,\"to\":10000}],\"labels\":{\"show\":true},\"invertColors\":false,\"style\":{\"bgFill\":\"#000\",\"bgColor\":false,\"labelColor\":false,\"subText\":\"\",\"fontSize\":29}},\"dimensions\":{\"metrics\":[{\"type\":\"vis_dimension\",\"accessor\":0,\"format\":{\"id\":\"number\",\"params\":{}}}]},\"addTooltip\":true,\"addLegend\":false,\"type\":\"metric\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}}]}"},"coreMigrationVersion":"8.7.1","created_at":"2023-07-20T15:26:57.180Z","id":"d04b5130-6e99-11ea-9266-1fd14ca6af34","migrationVersion":{"visualization":"8.5.0"},"references":[{"id":"logs-*","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"sort":[1689866817180,4254],"type":"visualization","updated_at":"2023-07-20T15:26:57.180Z","version":"WzQwODAsMV0="} {"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Security Onion - All Logs","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Security Onion - All Logs\",\"type\":\"metric\",\"params\":{\"metric\":{\"percentageMode\":false,\"useRanges\":false,\"colorSchema\":\"Green to Red\",\"metricColorMode\":\"None\",\"colorsRange\":[{\"type\":\"range\",\"from\":0,\"to\":10000}],\"labels\":{\"show\":true},\"invertColors\":false,\"style\":{\"bgFill\":\"#000\",\"bgColor\":false,\"labelColor\":false,\"subText\":\"\",\"fontSize\":29}},\"dimensions\":{\"metrics\":[{\"type\":\"vis_dimension\",\"accessor\":0,\"format\":{\"id\":\"number\",\"params\":{}}}]},\"addTooltip\":true,\"addLegend\":false,\"type\":\"metric\"},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}}]}"},"coreMigrationVersion":"8.7.1","created_at":"2023-07-20T15:26:57.180Z","id":"d04b5130-6e99-11ea-9266-1fd14ca6af34","migrationVersion":{"visualization":"8.5.0"},"references":[{"id":"logs-*","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"sort":[1689866817180,4254],"type":"visualization","updated_at":"2023-07-20T15:26:57.180Z","version":"WzQwODAsMV0="}
@@ -9,5 +9,5 @@ SESSIONCOOKIE=$(curl -K /opt/so/conf/elasticsearch/curl.config -c - -X GET http:
# Disable certain Features from showing up in the Kibana UI # Disable certain Features from showing up in the Kibana UI
echo echo
echo "Setting up default Kibana Space:" echo "Setting up default Kibana Space:"
curl -K /opt/so/conf/elasticsearch/curl.config -b "sid=$SESSIONCOOKIE" -L -X PUT "localhost:5601/api/spaces/space/default" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d' {"id":"default","name":"Default","disabledFeatures":["ml","enterpriseSearch","logs","infrastructure","apm","uptime","securitySolutionCasesV3","inventory","searchSynonyms","searchQueryRules","enterpriseSearchApplications","enterpriseSearchAnalytics","securitySolutionTimeline","securitySolutionNotes","securitySolutionRulesV4","securitySolutionAlertsV1","entityManager","slo","streams","anonymization","searchInferenceEndpoints","cloudConnect","queryActivity","automatic_import","stackAlerts","monitoring","dataQuality","actions"]} ' >> /opt/so/log/kibana/misc.log curl -K /opt/so/conf/elasticsearch/curl.config -b "sid=$SESSIONCOOKIE" -L -X PUT "localhost:5601/api/spaces/space/default" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d' {"id":"default","name":"Default","disabledFeatures":["ml","enterpriseSearch","logs","infrastructure","apm","uptime","monitoring","stackAlerts","actions","securitySolutionCasesV3","inventory","dataQuality","searchSynonyms","searchQueryRules","enterpriseSearchApplications","enterpriseSearchAnalytics","securitySolutionTimeline","securitySolutionNotes","securitySolutionRulesV1","entityManager","streams","cloudConnect","slo"]} ' >> /opt/so/log/kibana/misc.log
echo echo
-1
View File
@@ -27,7 +27,6 @@ kratos:
- uid: 928 - uid: 928
- gid: 928 - gid: 928
- home: /opt/so/conf/kratos - home: /opt/so/conf/kratos
- shell: /sbin/nologin
kratosdir: kratosdir:
file.directory: file.directory:
+3 -3
View File
@@ -19,8 +19,8 @@ so-kratos:
- hostname: kratos - hostname: kratos
- name: so-kratos - name: so-kratos
- networks: - networks:
- soauth: - sobridge:
- ipv4_address: {{ DOCKERMERGED.containers['so-kratos'].ips['soauth'] }} - ipv4_address: {{ DOCKERMERGED.containers['so-kratos'].ip }}
- binds: - binds:
- /opt/so/conf/kratos/:/kratos-conf:ro - /opt/so/conf/kratos/:/kratos-conf:ro
- /opt/so/log/kratos/:/kratos-log:rw - /opt/so/log/kratos/:/kratos-log:rw
@@ -71,7 +71,7 @@ delete_so-kratos_so-status.disabled:
wait_for_kratos: wait_for_kratos:
http.wait_for_successful_query: http.wait_for_successful_query:
- name: 'http://{{ DOCKERMERGED.containers['so-kratos'].ips['soauth'] }}:4434/' - name: 'http://{{ GLOBALS.manager }}:4434/'
- ssl: True - ssl: True
- verify_ssl: False - verify_ssl: False
- status: - status:
-2
View File
@@ -6,8 +6,6 @@ so-fix-salt-ldap_script:
file.managed: file.managed:
- name: /usr/sbin/so-fix-salt-ldap.py - name: /usr/sbin/so-fix-salt-ldap.py
- source: salt://libvirt/64962/scripts/so-fix-salt-ldap.py - source: salt://libvirt/64962/scripts/so-fix-salt-ldap.py
- user: root
- group: root
- mode: 744 - mode: 744
fix-salt-ldap: fix-salt-ldap:
-30
View File
@@ -220,36 +220,6 @@ logrotate:
- extension .log - extension .log
- dateext - dateext
- dateyesterday - dateyesterday
/opt/so/log/salt/virtual_node_manager:
- daily
- rotate 14
- missingok
- copytruncate
- compress
- create
- extension .log
- dateext
- dateyesterday
/opt/so/log/salt/so-salt-cloud:
- daily
- rotate 14
- missingok
- copytruncate
- compress
- create
- extension .log
- dateext
- dateyesterday
/opt/so/log/salt/so-soup-grid-highstate:
- daily
- rotate 14
- missingok
- copytruncate
- compress
- create
- extension .log
- dateext
- dateyesterday
/nsm/idh/*_x_log: /nsm/idh/*_x_log:
- daily - daily
- rotate 14 - rotate 14
-21
View File
@@ -140,27 +140,6 @@ logrotate:
multiline: True multiline: True
global: True global: True
forcedType: "[]string" forcedType: "[]string"
"/opt/so/log/salt/virtual_node_manager":
description: List of logrotate options for this file.
title: /opt/so/log/salt/virtual_node_manager
advanced: True
multiline: True
global: True
forcedType: "[]string"
"/opt/so/log/salt/so-salt-cloud":
description: List of logrotate options for this file.
title: /opt/so/log/salt/so-salt-cloud
advanced: True
multiline: True
global: True
forcedType: "[]string"
"/opt/so/log/salt/so-soup-grid-highstate":
description: List of logrotate options for this file.
title: /opt/so/log/salt/so-soup-grid-highstate
advanced: True
multiline: True
global: True
forcedType: "[]string"
"/nsm/idh/*_x_log": "/nsm/idh/*_x_log":
description: List of logrotate options for this file. description: List of logrotate options for this file.
title: /nsm/idh/*.log title: /nsm/idh/*.log
+5 -26
View File
@@ -35,14 +35,13 @@ logstash:
- uid: 931 - uid: 931
- gid: 931 - gid: 931
- home: /opt/so/conf/logstash - home: /opt/so/conf/logstash
- shell: /sbin/nologin
logstash_sbin: logstash_sbin:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://logstash/tools/sbin - source: salt://logstash/tools/sbin
- user: root - user: 931
- group: root - group: 939
- file_mode: 755 - file_mode: 755
#logstash_sbin_jinja: #logstash_sbin_jinja:
@@ -82,14 +81,6 @@ ls_custom_pipeline_conf_{{assigned_pipeline}}_{{pipeline}}:
{% for assigned_pipeline in ASSIGNED_PIPELINES %} {% for assigned_pipeline in ASSIGNED_PIPELINES %}
{# a blank per-pipeline setting falls back to the global logstash.yml value #}
{% set PARSED_OVERRIDES = LOGSTASH_MERGED.get('pipeline_settings', {}).get(assigned_pipeline, {}) %}
{% if PARSED_OVERRIDES is not mapping %}
{% do salt.log.warning('logstash: ignoring malformed pipeline_settings for pipeline ' ~ assigned_pipeline ~ '; expected a set of settings') %}
{% endif %}
{% set PIPELINE_OVERRIDES = PARSED_OVERRIDES if PARSED_OVERRIDES is mapping else {} %}
{% set THREADS = PIPELINE_OVERRIDES.get('pipeline_x_workers') or LOGSTASH_MERGED.config.pipeline_x_workers %}
{% set BATCH = PIPELINE_OVERRIDES.get('pipeline_x_batch_x_size') or LOGSTASH_MERGED.config.pipeline_x_batch_x_size %}
{% for CONFIGFILE in LOGSTASH_MERGED.defined_pipelines[assigned_pipeline] %} {% for CONFIGFILE in LOGSTASH_MERGED.defined_pipelines[assigned_pipeline] %}
ls_pipeline_{{assigned_pipeline}}_{{CONFIGFILE.split('.')[0] | replace("/","_") }}: ls_pipeline_{{assigned_pipeline}}_{{CONFIGFILE.split('.')[0] | replace("/","_") }}:
file.managed: file.managed:
@@ -101,8 +92,8 @@ ls_pipeline_{{assigned_pipeline}}_{{CONFIGFILE.split('.')[0] | replace("/","_")
GLOBALS: {{ GLOBALS }} GLOBALS: {{ GLOBALS }}
ES_USER: "{{ salt['pillar.get']('elasticsearch:auth:users:so_elastic_user:user', '') }}" ES_USER: "{{ salt['pillar.get']('elasticsearch:auth:users:so_elastic_user:user', '') }}"
ES_PASS: "{{ salt['pillar.get']('elasticsearch:auth:users:so_elastic_user:pass', '') }}" ES_PASS: "{{ salt['pillar.get']('elasticsearch:auth:users:so_elastic_user:pass', '') }}"
THREADS: {{ THREADS }} THREADS: {{ LOGSTASH_MERGED.config.pipeline_x_workers }}
BATCH: {{ BATCH }} BATCH: {{ LOGSTASH_MERGED.config.pipeline_x_batch_x_size }}
{% else %} {% else %}
- name: /opt/so/conf/logstash/pipelines/{{assigned_pipeline}}/{{CONFIGFILE.split('/')[1]}} - name: /opt/so/conf/logstash/pipelines/{{assigned_pipeline}}/{{CONFIGFILE.split('/')[1]}}
{% endif %} {% endif %}
@@ -134,14 +125,6 @@ lspipelinesyml:
- defaults: - defaults:
ASSIGNED_PIPELINES: {{ ASSIGNED_PIPELINES }} ASSIGNED_PIPELINES: {{ ASSIGNED_PIPELINES }}
lslog4j2:
file.managed:
- name: /opt/so/conf/logstash/etc/log4j2.properties
- source: salt://logstash/etc/log4j2.properties.jinja
- template: jinja
- user: 931
- group: 939
lsetcsync: lsetcsync:
file.recurse: file.recurse:
- name: /opt/so/conf/logstash/etc - name: /opt/so/conf/logstash/etc
@@ -150,11 +133,7 @@ lsetcsync:
- group: 939 - group: 939
- template: jinja - template: jinja
- clean: True - clean: True
{#- both names are matched: the .jinja source so the recurse does not copy it verbatim, - exclude_pat: pipelines*
and the rendered file so clean: True does not delete what lslog4j2 wrote #}
- exclude_pat:
- pipelines*
- log4j2.properties*
- defaults: - defaults:
LOGSTASH_MERGED: {{ LOGSTASH_MERGED }} LOGSTASH_MERGED: {{ LOGSTASH_MERGED }}
-400
View File
@@ -42,11 +42,6 @@ logstash:
custom2: [] custom2: []
custom3: [] custom3: []
custom4: [] custom4: []
custom5: []
custom6: []
custom7: []
custom8: []
custom9: []
pipeline_config: pipeline_config:
custom001: |- custom001: |-
filter { filter {
@@ -65,405 +60,10 @@ logstash:
custom008: PLACEHOLDER custom008: PLACEHOLDER
custom009: PLACEHOLDER custom009: PLACEHOLDER
custom010: PLACEHOLDER custom010: PLACEHOLDER
pipeline_settings:
fleet:
pipeline_x_workers: ''
pipeline_x_batch_x_size: ''
pipeline_x_batch_x_delay: ''
pipeline_x_batch_x_metrics_x_sampling_mode: ''
pipeline_x_ordered: ''
pipeline_x_ecs_compatibility: ''
pipeline_x_reloadable: ''
queue_x_type: ''
queue_x_max_bytes: ''
queue_x_page_capacity: ''
queue_x_max_events: ''
queue_x_checkpoint_x_acks: ''
queue_x_checkpoint_x_writes: ''
queue_x_checkpoint_x_interval: ''
queue_x_checkpoint_x_retry: ''
queue_x_compression: ''
queue_x_drain: ''
dead_letter_queue_x_enable: ''
dead_letter_queue_x_max_bytes: ''
dead_letter_queue_x_flush_interval: ''
dead_letter_queue_x_flush_check_interval: ''
dead_letter_queue_x_storage_policy: ''
dead_letter_queue_x_retain_x_age: ''
path_x_queue: ''
path_x_dead_letter_queue: ''
config_x_debug: ''
config_x_support_escapes: ''
manager:
pipeline_x_workers: ''
pipeline_x_batch_x_size: ''
pipeline_x_batch_x_delay: ''
pipeline_x_batch_x_metrics_x_sampling_mode: ''
pipeline_x_ordered: ''
pipeline_x_ecs_compatibility: ''
pipeline_x_reloadable: ''
queue_x_type: ''
queue_x_max_bytes: ''
queue_x_page_capacity: ''
queue_x_max_events: ''
queue_x_checkpoint_x_acks: ''
queue_x_checkpoint_x_writes: ''
queue_x_checkpoint_x_interval: ''
queue_x_checkpoint_x_retry: ''
queue_x_compression: ''
queue_x_drain: ''
dead_letter_queue_x_enable: ''
dead_letter_queue_x_max_bytes: ''
dead_letter_queue_x_flush_interval: ''
dead_letter_queue_x_flush_check_interval: ''
dead_letter_queue_x_storage_policy: ''
dead_letter_queue_x_retain_x_age: ''
path_x_queue: ''
path_x_dead_letter_queue: ''
config_x_debug: ''
config_x_support_escapes: ''
receiver:
pipeline_x_workers: ''
pipeline_x_batch_x_size: ''
pipeline_x_batch_x_delay: ''
pipeline_x_batch_x_metrics_x_sampling_mode: ''
pipeline_x_ordered: ''
pipeline_x_ecs_compatibility: ''
pipeline_x_reloadable: ''
queue_x_type: ''
queue_x_max_bytes: ''
queue_x_page_capacity: ''
queue_x_max_events: ''
queue_x_checkpoint_x_acks: ''
queue_x_checkpoint_x_writes: ''
queue_x_checkpoint_x_interval: ''
queue_x_checkpoint_x_retry: ''
queue_x_compression: ''
queue_x_drain: ''
dead_letter_queue_x_enable: ''
dead_letter_queue_x_max_bytes: ''
dead_letter_queue_x_flush_interval: ''
dead_letter_queue_x_flush_check_interval: ''
dead_letter_queue_x_storage_policy: ''
dead_letter_queue_x_retain_x_age: ''
path_x_queue: ''
path_x_dead_letter_queue: ''
config_x_debug: ''
config_x_support_escapes: ''
search:
pipeline_x_workers: ''
pipeline_x_batch_x_size: ''
pipeline_x_batch_x_delay: ''
pipeline_x_batch_x_metrics_x_sampling_mode: ''
pipeline_x_ordered: ''
pipeline_x_ecs_compatibility: ''
pipeline_x_reloadable: ''
queue_x_type: ''
queue_x_max_bytes: ''
queue_x_page_capacity: ''
queue_x_max_events: ''
queue_x_checkpoint_x_acks: ''
queue_x_checkpoint_x_writes: ''
queue_x_checkpoint_x_interval: ''
queue_x_checkpoint_x_retry: ''
queue_x_compression: ''
queue_x_drain: ''
dead_letter_queue_x_enable: ''
dead_letter_queue_x_max_bytes: ''
dead_letter_queue_x_flush_interval: ''
dead_letter_queue_x_flush_check_interval: ''
dead_letter_queue_x_storage_policy: ''
dead_letter_queue_x_retain_x_age: ''
path_x_queue: ''
path_x_dead_letter_queue: ''
config_x_debug: ''
config_x_support_escapes: ''
custom0:
pipeline_x_workers: ''
pipeline_x_batch_x_size: ''
pipeline_x_batch_x_delay: ''
pipeline_x_batch_x_metrics_x_sampling_mode: ''
pipeline_x_ordered: ''
pipeline_x_ecs_compatibility: ''
pipeline_x_reloadable: ''
queue_x_type: ''
queue_x_max_bytes: ''
queue_x_page_capacity: ''
queue_x_max_events: ''
queue_x_checkpoint_x_acks: ''
queue_x_checkpoint_x_writes: ''
queue_x_checkpoint_x_interval: ''
queue_x_checkpoint_x_retry: ''
queue_x_compression: ''
queue_x_drain: ''
dead_letter_queue_x_enable: ''
dead_letter_queue_x_max_bytes: ''
dead_letter_queue_x_flush_interval: ''
dead_letter_queue_x_flush_check_interval: ''
dead_letter_queue_x_storage_policy: ''
dead_letter_queue_x_retain_x_age: ''
path_x_queue: ''
path_x_dead_letter_queue: ''
config_x_debug: ''
config_x_support_escapes: ''
custom1:
pipeline_x_workers: ''
pipeline_x_batch_x_size: ''
pipeline_x_batch_x_delay: ''
pipeline_x_batch_x_metrics_x_sampling_mode: ''
pipeline_x_ordered: ''
pipeline_x_ecs_compatibility: ''
pipeline_x_reloadable: ''
queue_x_type: ''
queue_x_max_bytes: ''
queue_x_page_capacity: ''
queue_x_max_events: ''
queue_x_checkpoint_x_acks: ''
queue_x_checkpoint_x_writes: ''
queue_x_checkpoint_x_interval: ''
queue_x_checkpoint_x_retry: ''
queue_x_compression: ''
queue_x_drain: ''
dead_letter_queue_x_enable: ''
dead_letter_queue_x_max_bytes: ''
dead_letter_queue_x_flush_interval: ''
dead_letter_queue_x_flush_check_interval: ''
dead_letter_queue_x_storage_policy: ''
dead_letter_queue_x_retain_x_age: ''
path_x_queue: ''
path_x_dead_letter_queue: ''
config_x_debug: ''
config_x_support_escapes: ''
custom2:
pipeline_x_workers: ''
pipeline_x_batch_x_size: ''
pipeline_x_batch_x_delay: ''
pipeline_x_batch_x_metrics_x_sampling_mode: ''
pipeline_x_ordered: ''
pipeline_x_ecs_compatibility: ''
pipeline_x_reloadable: ''
queue_x_type: ''
queue_x_max_bytes: ''
queue_x_page_capacity: ''
queue_x_max_events: ''
queue_x_checkpoint_x_acks: ''
queue_x_checkpoint_x_writes: ''
queue_x_checkpoint_x_interval: ''
queue_x_checkpoint_x_retry: ''
queue_x_compression: ''
queue_x_drain: ''
dead_letter_queue_x_enable: ''
dead_letter_queue_x_max_bytes: ''
dead_letter_queue_x_flush_interval: ''
dead_letter_queue_x_flush_check_interval: ''
dead_letter_queue_x_storage_policy: ''
dead_letter_queue_x_retain_x_age: ''
path_x_queue: ''
path_x_dead_letter_queue: ''
config_x_debug: ''
config_x_support_escapes: ''
custom3:
pipeline_x_workers: ''
pipeline_x_batch_x_size: ''
pipeline_x_batch_x_delay: ''
pipeline_x_batch_x_metrics_x_sampling_mode: ''
pipeline_x_ordered: ''
pipeline_x_ecs_compatibility: ''
pipeline_x_reloadable: ''
queue_x_type: ''
queue_x_max_bytes: ''
queue_x_page_capacity: ''
queue_x_max_events: ''
queue_x_checkpoint_x_acks: ''
queue_x_checkpoint_x_writes: ''
queue_x_checkpoint_x_interval: ''
queue_x_checkpoint_x_retry: ''
queue_x_compression: ''
queue_x_drain: ''
dead_letter_queue_x_enable: ''
dead_letter_queue_x_max_bytes: ''
dead_letter_queue_x_flush_interval: ''
dead_letter_queue_x_flush_check_interval: ''
dead_letter_queue_x_storage_policy: ''
dead_letter_queue_x_retain_x_age: ''
path_x_queue: ''
path_x_dead_letter_queue: ''
config_x_debug: ''
config_x_support_escapes: ''
custom4:
pipeline_x_workers: ''
pipeline_x_batch_x_size: ''
pipeline_x_batch_x_delay: ''
pipeline_x_batch_x_metrics_x_sampling_mode: ''
pipeline_x_ordered: ''
pipeline_x_ecs_compatibility: ''
pipeline_x_reloadable: ''
queue_x_type: ''
queue_x_max_bytes: ''
queue_x_page_capacity: ''
queue_x_max_events: ''
queue_x_checkpoint_x_acks: ''
queue_x_checkpoint_x_writes: ''
queue_x_checkpoint_x_interval: ''
queue_x_checkpoint_x_retry: ''
queue_x_compression: ''
queue_x_drain: ''
dead_letter_queue_x_enable: ''
dead_letter_queue_x_max_bytes: ''
dead_letter_queue_x_flush_interval: ''
dead_letter_queue_x_flush_check_interval: ''
dead_letter_queue_x_storage_policy: ''
dead_letter_queue_x_retain_x_age: ''
path_x_queue: ''
path_x_dead_letter_queue: ''
config_x_debug: ''
config_x_support_escapes: ''
custom5:
pipeline_x_workers: ''
pipeline_x_batch_x_size: ''
pipeline_x_batch_x_delay: ''
pipeline_x_batch_x_metrics_x_sampling_mode: ''
pipeline_x_ordered: ''
pipeline_x_ecs_compatibility: ''
pipeline_x_reloadable: ''
queue_x_type: ''
queue_x_max_bytes: ''
queue_x_page_capacity: ''
queue_x_max_events: ''
queue_x_checkpoint_x_acks: ''
queue_x_checkpoint_x_writes: ''
queue_x_checkpoint_x_interval: ''
queue_x_checkpoint_x_retry: ''
queue_x_compression: ''
queue_x_drain: ''
dead_letter_queue_x_enable: ''
dead_letter_queue_x_max_bytes: ''
dead_letter_queue_x_flush_interval: ''
dead_letter_queue_x_flush_check_interval: ''
dead_letter_queue_x_storage_policy: ''
dead_letter_queue_x_retain_x_age: ''
path_x_queue: ''
path_x_dead_letter_queue: ''
config_x_debug: ''
config_x_support_escapes: ''
custom6:
pipeline_x_workers: ''
pipeline_x_batch_x_size: ''
pipeline_x_batch_x_delay: ''
pipeline_x_batch_x_metrics_x_sampling_mode: ''
pipeline_x_ordered: ''
pipeline_x_ecs_compatibility: ''
pipeline_x_reloadable: ''
queue_x_type: ''
queue_x_max_bytes: ''
queue_x_page_capacity: ''
queue_x_max_events: ''
queue_x_checkpoint_x_acks: ''
queue_x_checkpoint_x_writes: ''
queue_x_checkpoint_x_interval: ''
queue_x_checkpoint_x_retry: ''
queue_x_compression: ''
queue_x_drain: ''
dead_letter_queue_x_enable: ''
dead_letter_queue_x_max_bytes: ''
dead_letter_queue_x_flush_interval: ''
dead_letter_queue_x_flush_check_interval: ''
dead_letter_queue_x_storage_policy: ''
dead_letter_queue_x_retain_x_age: ''
path_x_queue: ''
path_x_dead_letter_queue: ''
config_x_debug: ''
config_x_support_escapes: ''
custom7:
pipeline_x_workers: ''
pipeline_x_batch_x_size: ''
pipeline_x_batch_x_delay: ''
pipeline_x_batch_x_metrics_x_sampling_mode: ''
pipeline_x_ordered: ''
pipeline_x_ecs_compatibility: ''
pipeline_x_reloadable: ''
queue_x_type: ''
queue_x_max_bytes: ''
queue_x_page_capacity: ''
queue_x_max_events: ''
queue_x_checkpoint_x_acks: ''
queue_x_checkpoint_x_writes: ''
queue_x_checkpoint_x_interval: ''
queue_x_checkpoint_x_retry: ''
queue_x_compression: ''
queue_x_drain: ''
dead_letter_queue_x_enable: ''
dead_letter_queue_x_max_bytes: ''
dead_letter_queue_x_flush_interval: ''
dead_letter_queue_x_flush_check_interval: ''
dead_letter_queue_x_storage_policy: ''
dead_letter_queue_x_retain_x_age: ''
path_x_queue: ''
path_x_dead_letter_queue: ''
config_x_debug: ''
config_x_support_escapes: ''
custom8:
pipeline_x_workers: ''
pipeline_x_batch_x_size: ''
pipeline_x_batch_x_delay: ''
pipeline_x_batch_x_metrics_x_sampling_mode: ''
pipeline_x_ordered: ''
pipeline_x_ecs_compatibility: ''
pipeline_x_reloadable: ''
queue_x_type: ''
queue_x_max_bytes: ''
queue_x_page_capacity: ''
queue_x_max_events: ''
queue_x_checkpoint_x_acks: ''
queue_x_checkpoint_x_writes: ''
queue_x_checkpoint_x_interval: ''
queue_x_checkpoint_x_retry: ''
queue_x_compression: ''
queue_x_drain: ''
dead_letter_queue_x_enable: ''
dead_letter_queue_x_max_bytes: ''
dead_letter_queue_x_flush_interval: ''
dead_letter_queue_x_flush_check_interval: ''
dead_letter_queue_x_storage_policy: ''
dead_letter_queue_x_retain_x_age: ''
path_x_queue: ''
path_x_dead_letter_queue: ''
config_x_debug: ''
config_x_support_escapes: ''
custom9:
pipeline_x_workers: ''
pipeline_x_batch_x_size: ''
pipeline_x_batch_x_delay: ''
pipeline_x_batch_x_metrics_x_sampling_mode: ''
pipeline_x_ordered: ''
pipeline_x_ecs_compatibility: ''
pipeline_x_reloadable: ''
queue_x_type: ''
queue_x_max_bytes: ''
queue_x_page_capacity: ''
queue_x_max_events: ''
queue_x_checkpoint_x_acks: ''
queue_x_checkpoint_x_writes: ''
queue_x_checkpoint_x_interval: ''
queue_x_checkpoint_x_retry: ''
queue_x_compression: ''
queue_x_drain: ''
dead_letter_queue_x_enable: ''
dead_letter_queue_x_max_bytes: ''
dead_letter_queue_x_flush_interval: ''
dead_letter_queue_x_flush_check_interval: ''
dead_letter_queue_x_storage_policy: ''
dead_letter_queue_x_retain_x_age: ''
path_x_queue: ''
path_x_dead_letter_queue: ''
config_x_debug: ''
config_x_support_escapes: ''
settings: settings:
lsheap: 500m lsheap: 500m
config: config:
api_x_http_x_host: 0.0.0.0 api_x_http_x_host: 0.0.0.0
log_x_level: info
log_x_format: plain
path_x_logs: /var/log/logstash path_x_logs: /var/log/logstash
pipeline_x_workers: 1 pipeline_x_workers: 1
pipeline_x_batch_x_size: 125 pipeline_x_batch_x_size: 125
-2
View File
@@ -105,8 +105,6 @@ so-logstash:
{% endif %} {% endif %}
- watch: - watch:
- file: lsetcsync - file: lsetcsync
- file: lslog4j2
- file: lspipelinesyml
- file: trusttheca - file: trusttheca
{% if GLOBALS.is_manager %} {% if GLOBALS.is_manager %}
- file: elasticsearch_cacerts - file: elasticsearch_cacerts
@@ -1,4 +1,3 @@
{%- from 'logstash/map.jinja' import LOGSTASH_MERGED -%}
status = error status = error
name = LogstashPropertiesConfig name = LogstashPropertiesConfig
@@ -17,14 +16,8 @@ name = LogstashPropertiesConfig
appender.rolling.type = RollingFile appender.rolling.type = RollingFile
appender.rolling.name = rolling appender.rolling.name = rolling
appender.rolling.fileName = /var/log/logstash/logstash.log appender.rolling.fileName = /var/log/logstash/logstash.log
{%- if LOGSTASH_MERGED.config.get('log_x_format', 'plain') == 'json' %}
appender.rolling.layout.type = JSONLayout
appender.rolling.layout.compact = true
appender.rolling.layout.eventEol = true
{%- else %}
appender.rolling.layout.type = PatternLayout appender.rolling.layout.type = PatternLayout
appender.rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c] %.10000m%n appender.rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c] %.10000m%n
{%- endif %}
appender.rolling.filePattern = /var/log/logstash/logstash-%d{yyyy-MM-dd}.log.gz appender.rolling.filePattern = /var/log/logstash/logstash-%d{yyyy-MM-dd}.log.gz
appender.rolling.policies.type = Policies appender.rolling.policies.type = Policies
appender.rolling.policies.time.type = TimeBasedTriggeringPolicy appender.rolling.policies.time.type = TimeBasedTriggeringPolicy
@@ -39,5 +32,7 @@ appender.rolling.strategy.action.condition.type = IfFileName
appender.rolling.strategy.action.condition.glob = *.gz appender.rolling.strategy.action.condition.glob = *.gz
appender.rolling.strategy.action.condition.nested_condition.type = IfLastModified appender.rolling.strategy.action.condition.nested_condition.type = IfLastModified
appender.rolling.strategy.action.condition.nested_condition.age = 7D appender.rolling.strategy.action.condition.nested_condition.age = 7D
rootLogger.level = ${sys:ls.log.level} rootLogger.level = info
rootLogger.appenderRef.rolling.ref = rolling rootLogger.appenderRef.rolling.ref = rolling
#rootLogger.level = ${sys:ls.log.level}
#rootLogger.appenderRef.console.ref = ${sys:ls.log.format}_console
-13
View File
@@ -1,17 +1,4 @@
{%- from 'logstash/map.jinja' import LOGSTASH_MERGED %}
{%- set PIPELINE_SETTINGS = LOGSTASH_MERGED.get('pipeline_settings', {}) %}
{%- for assigned_pipeline in ASSIGNED_PIPELINES %} {%- for assigned_pipeline in ASSIGNED_PIPELINES %}
- pipeline.id: {{ assigned_pipeline }} - pipeline.id: {{ assigned_pipeline }}
path.config: "/usr/share/logstash/pipelines/{{ assigned_pipeline }}/" path.config: "/usr/share/logstash/pipelines/{{ assigned_pipeline }}/"
{%- set extra = PIPELINE_SETTINGS.get(assigned_pipeline, {}) %}
{%- if extra is mapping %}
{#- values are emitted unquoted so yaml re-infers the type logstash expects:
4 as an integer, false as a boolean, 1024mb and auto as strings #}
{%- for key, value in extra | dictsort %}
{%- set rendered = key | replace('_x_', '.') %}
{%- if value not in ['', None] and rendered not in ['pipeline.id', 'path.config'] %}
{{ rendered }}: {{ value }}
{%- endif %}
{%- endfor %}
{%- endif %}
{% endfor -%} {% endfor -%}
-380
View File
@@ -16,7 +16,6 @@ logstash:
heavynode: *assigned_pipelines heavynode: *assigned_pipelines
searchnode: *assigned_pipelines searchnode: *assigned_pipelines
manager: *assigned_pipelines manager: *assigned_pipelines
managerhype: *assigned_pipelines
managersearch: *assigned_pipelines managersearch: *assigned_pipelines
fleet: *assigned_pipelines fleet: *assigned_pipelines
defined_pipelines: defined_pipelines:
@@ -35,11 +34,6 @@ logstash:
custom2: *defined_pipelines custom2: *defined_pipelines
custom3: *defined_pipelines custom3: *defined_pipelines
custom4: *defined_pipelines custom4: *defined_pipelines
custom5: *defined_pipelines
custom6: *defined_pipelines
custom7: *defined_pipelines
custom8: *defined_pipelines
custom9: *defined_pipelines
pipeline_config: pipeline_config:
custom001: &pipeline_config custom001: &pipeline_config
description: Pipeline configuration for Logstash description: Pipeline configuration for Logstash
@@ -57,351 +51,6 @@ logstash:
custom008: *pipeline_config custom008: *pipeline_config
custom009: *pipeline_config custom009: *pipeline_config
custom010: *pipeline_config custom010: *pipeline_config
pipeline_settings:
manager: &pipeline_settings
pipeline_x_workers:
description: >-
Number of worker threads that run filters and outputs for this pipeline. May be set higher
than the CPU core count when outputs spend time waiting on I/O. Leave blank to use the value
from logstash.yml.
title: pipeline.workers
regex: '^$|^[1-9][0-9]*$'
regexFailureMessage: Must be blank, or a positive whole number.
advanced: True
global: False
helpLink: logstash
pipeline_x_batch_x_size:
description: >-
Maximum number of events an individual worker thread collects before running filters and
outputs. Larger batches are more efficient but increase heap use; total in-flight events is
workers multiplied by batch size. Leave blank to use the value from logstash.yml.
title: pipeline.batch.size
regex: '^$|^[1-9][0-9]*$'
regexFailureMessage: Must be blank, or a positive whole number.
advanced: True
global: False
helpLink: logstash
pipeline_x_batch_x_delay:
description: >-
Milliseconds a worker waits for the next event before running a batch that is not yet full.
Leave blank to use the value from logstash.yml.
title: pipeline.batch.delay
regex: '^$|^[0-9]+$'
regexFailureMessage: Must be blank, or a whole number.
advanced: True
global: False
helpLink: logstash
pipeline_x_batch_x_metrics_x_sampling_mode:
description: >-
Controls how often batch size metrics are collected for this pipeline, which helps tune
pipeline.batch.size to the batch sizes actually being processed. Fuller sampling consumes
additional heap. Elastic marks this setting as a technical preview that may change in a
future release. Leave blank to use the value from logstash.yml.
title: pipeline.batch.metrics.sampling_mode
options:
- ''
- 'disabled'
- 'minimal'
- 'full'
advanced: True
global: False
helpLink: logstash
pipeline_x_ordered:
description: >-
Whether event order is preserved through this pipeline. auto enables ordering only when
pipeline.workers is explicitly set to 1, and does nothing otherwise. Setting this to true
requires pipeline.workers to be 1 as well; with more workers this pipeline fails to start.
Leave blank to use the value from logstash.yml.
title: pipeline.ordered
options:
- ''
- 'auto'
- 'true'
- 'false'
advanced: True
global: False
helpLink: logstash
pipeline_x_ecs_compatibility:
description: >-
Elastic Common Schema compatibility mode for plugins in this pipeline. Security Onion sets
this globally and it should rarely be changed per pipeline. Elastic considers values other
than disabled to be BETA, and they may produce unintended consequences when upgrading
Logstash. Leave blank to use the value from logstash.yml.
title: pipeline.ecs_compatibility
options:
- ''
- 'disabled'
- 'v1'
- 'v8'
advanced: True
global: False
helpLink: logstash
pipeline_x_reloadable:
description: >-
Whether this pipeline may be reloaded when its configuration changes. Leave blank to use the
value from logstash.yml.
title: pipeline.reloadable
options:
- ''
- 'true'
- 'false'
advanced: True
global: False
helpLink: logstash
queue_x_type:
description: >-
Queue backing this pipeline. persisted buffers events to disk under /nsm/logstash so they
survive a restart, at some throughput cost; memory does not. Leave blank to use the value
from logstash.yml.
title: queue.type
options:
- ''
- 'memory'
- 'persisted'
advanced: True
global: False
helpLink: logstash
queue_x_max_bytes:
description: >-
Total capacity of the persistent queue for this pipeline, in bytes. Only applies when
queue.type is persisted. The disk backing /nsm/logstash must have room for this much data or
the pipeline fails to start, reporting that it was unable to allocate the space. If both
queue.max_events and queue.max_bytes are set, whichever is reached first applies. Leave
blank to use the value from logstash.yml.
title: queue.max_bytes
regex: '^$|^[0-9]+$|^[0-9]+(\.[0-9]+)?\s*(b|kb?|mb?|gb?|tb?|pb?)$'
regexFailureMessage: Must be blank, or a size such as 512mb, 1gb, or 64k. Units are lowercase.
advanced: True
global: False
helpLink: logstash
queue_x_page_capacity:
description: >-
Size of the individual append-only page data files that make up the persistent queue for
this pipeline. Only applies when queue.type is persisted. Leave blank to use the value from
logstash.yml.
title: queue.page_capacity
regex: '^$|^[0-9]+$|^[0-9]+(\.[0-9]+)?\s*(b|kb?|mb?|gb?|tb?|pb?)$'
regexFailureMessage: Must be blank, or a size such as 512mb, 1gb, or 64k. Units are lowercase.
advanced: True
global: False
helpLink: logstash
queue_x_max_events:
description: >-
Maximum number of unread events in the persistent queue for this pipeline. 0 means
unlimited. Only applies when queue.type is persisted. Leave blank to use the value from
logstash.yml.
title: queue.max_events
regex: '^$|^[0-9]+$'
regexFailureMessage: Must be blank, or a whole number.
advanced: True
global: False
helpLink: logstash
queue_x_checkpoint_x_acks:
description: >-
Maximum number of acknowledged events before a checkpoint is forced. 0 means unlimited. Only
applies when queue.type is persisted. Leave blank to use the value from logstash.yml.
title: queue.checkpoint.acks
regex: '^$|^[0-9]+$'
regexFailureMessage: Must be blank, or a whole number.
advanced: True
global: False
helpLink: logstash
queue_x_checkpoint_x_writes:
description: >-
Maximum number of written events before a checkpoint is forced. Setting this to 1 gives
maximum durability at a severe performance cost. 0 means unlimited. Only applies when
queue.type is persisted. Leave blank to use the value from logstash.yml.
title: queue.checkpoint.writes
regex: '^$|^[0-9]+$'
regexFailureMessage: Must be blank, or a whole number.
advanced: True
global: False
helpLink: logstash
queue_x_checkpoint_x_interval:
description: >-
Milliseconds between forced checkpoints on the persistent queue head page. 0 eliminates
periodic checkpoints. Deprecated by Elastic as of Logstash 9.1. Only applies when queue.type
is persisted. Leave blank to use the value from logstash.yml.
title: queue.checkpoint.interval
regex: '^$|^[0-9]+$'
regexFailureMessage: Must be blank, or a whole number.
advanced: True
global: False
helpLink: logstash
queue_x_checkpoint_x_retry:
description: >-
When enabled, Logstash retries four times per attempted checkpoint write that fails; later
errors are not retried. Elastic describes this as a workaround for failed checkpoint writes
seen only on Windows and on filesystems with non-standard behaviour such as SANs, and does
not recommend enabling it otherwise. Only applies when queue.type is persisted. Leave blank
to use the value from logstash.yml.
title: queue.checkpoint.retry
options:
- ''
- 'true'
- 'false'
advanced: True
global: False
helpLink: logstash
queue_x_compression:
description: >-
Compression applied to persistent queue pages for this pipeline, trading CPU for disk: speed
favours the fastest operation, size the smallest files, and balanced sits between them. Once
compressed events have been written, that queue cannot be read by Logstash releases earlier
than 9.2. Only applies when queue.type is persisted. Leave blank to use the value from
logstash.yml.
title: queue.compression
options:
- ''
- 'none'
- 'speed'
- 'balanced'
- 'size'
advanced: True
global: False
helpLink: logstash
queue_x_drain:
description: >-
When enabled, Logstash waits for the persistent queue to drain before shutting down this
pipeline. Draining a large queue makes shutdown take considerably longer. Only applies when
queue.type is persisted. Leave blank to use the value from logstash.yml.
title: queue.drain
options:
- ''
- 'true'
- 'false'
advanced: True
global: False
helpLink: logstash
dead_letter_queue_x_enable:
description: >-
Whether events this pipeline cannot process are written to a dead letter queue instead of
being dropped. Leave blank to use the value from logstash.yml.
title: dead_letter_queue.enable
options:
- ''
- 'true'
- 'false'
advanced: True
global: False
helpLink: logstash
dead_letter_queue_x_max_bytes:
description: >-
Total capacity of the dead letter queue for this pipeline, in bytes. Only applies when
dead_letter_queue.enable is true. Leave blank to use the value from logstash.yml.
title: dead_letter_queue.max_bytes
regex: '^$|^[0-9]+$|^[0-9]+(\.[0-9]+)?\s*(b|kb?|mb?|gb?|tb?|pb?)$'
regexFailureMessage: Must be blank, or a size such as 512mb, 1gb, or 64k. Units are lowercase.
advanced: True
global: False
helpLink: logstash
dead_letter_queue_x_flush_interval:
description: >-
Milliseconds before an incomplete dead letter queue segment is flushed and made available to
the dead_letter_queue input. Lower values write more, smaller segment files; higher values
add latency before events can be read. Only applies when dead_letter_queue.enable is true.
Leave blank to use the value from logstash.yml.
title: dead_letter_queue.flush_interval
regex: '^$|^[0-9]+$'
regexFailureMessage: Must be blank, or a whole number.
advanced: True
global: False
helpLink: logstash
dead_letter_queue_x_flush_check_interval:
description: >-
Milliseconds between checks for a stale dead letter queue segment needing a flush. Cannot be
set lower than 1000. Smaller values rotate segments sooner at the cost of CPU. Only applies
when dead_letter_queue.enable is true. Leave blank to use the value from logstash.yml.
title: dead_letter_queue.flush_check_interval
regex: '^$|^[0-9]+$'
regexFailureMessage: Must be blank, or a whole number.
advanced: True
global: False
helpLink: logstash
dead_letter_queue_x_storage_policy:
description: >-
Action taken when dead_letter_queue.max_bytes is reached: drop_newer stops accepting new
events, drop_older removes the oldest events to make room. Only applies when
dead_letter_queue.enable is true. Leave blank to use the value from logstash.yml.
title: dead_letter_queue.storage_policy
options:
- ''
- 'drop_newer'
- 'drop_older'
advanced: True
global: False
helpLink: logstash
dead_letter_queue_x_retain_x_age:
description: >-
How long an event is kept in the dead letter queue before Logstash removes it, such as 5d.
Units are d, h, m and s; there is no default unit, so one must be given. Only applies when
dead_letter_queue.enable is true. Leave blank to use the value from logstash.yml.
title: dead_letter_queue.retain.age
regex: '^$|^[0-9]+\s*[dhms]$'
regexFailureMessage: Must be blank, or a number followed by d, h, m, or s, such as 5d.
advanced: True
global: False
helpLink: logstash
path_x_queue:
description: >-
Directory inside the Logstash container holding the persistent queue for this pipeline. The
default lives under the /nsm/logstash bind mount; a path outside it will not survive a
container restart. Logstash creates the directory if it is missing, requires it to be
writable, and refuses to start if the path is a symlink. Only applies when queue.type is
persisted. Leave blank to use the value from logstash.yml.
title: path.queue
advanced: True
global: False
helpLink: logstash
path_x_dead_letter_queue:
description: >-
Directory inside the Logstash container holding the dead letter queue for this pipeline. The
default lives under the /nsm/logstash bind mount; a path outside it will not survive a
container restart. Logstash creates the directory if it is missing, requires it to be
writable, and refuses to start if the path is a symlink. Only applies when
dead_letter_queue.enable is true. Leave blank to use the value from logstash.yml.
title: path.dead_letter_queue
advanced: True
global: False
helpLink: logstash
config_x_debug:
description: >-
Whether the fully compiled configuration for this pipeline is written to the log. The output
may contain sensitive values from the pipeline configuration. Leave blank to use the value
from logstash.yml.
title: config.debug
options:
- ''
- 'true'
- 'false'
advanced: True
global: False
helpLink: logstash
config_x_support_escapes:
description: >-
Whether escape sequences such as \n and \t in this pipeline's quoted strings are
interpreted. Leave blank to use the value from logstash.yml.
title: config.support_escapes
options:
- ''
- 'true'
- 'false'
advanced: True
global: False
helpLink: logstash
fleet: *pipeline_settings
receiver: *pipeline_settings
search: *pipeline_settings
custom0: *pipeline_settings
custom1: *pipeline_settings
custom2: *pipeline_settings
custom3: *pipeline_settings
custom4: *pipeline_settings
custom5: *pipeline_settings
custom6: *pipeline_settings
custom7: *pipeline_settings
custom8: *pipeline_settings
custom9: *pipeline_settings
settings: settings:
lsheap: lsheap:
description: Heap size to use for logstash description: Heap size to use for logstash
@@ -413,35 +62,6 @@ logstash:
helpLink: logstash helpLink: logstash
readonly: True readonly: True
advanced: True advanced: True
log_x_level:
description: >-
Verbosity of the Logstash log at /opt/so/log/logstash/logstash.log. debug and trace produce
a very large volume of log data on a busy node and should be used only while troubleshooting;
the log rotates at 1GB and rotated files are deleted after 7 days. Setting this to debug is
also what makes the per-pipeline config.debug setting emit anything.
title: log.level
options:
- 'fatal'
- 'error'
- 'warn'
- 'info'
- 'debug'
- 'trace'
advanced: True
global: False
helpLink: logstash
log_x_format:
description: >-
Layout of the Logstash log. plain writes human readable lines; json writes one JSON object
per line, which is easier to parse but harder to read directly. The file name and location
do not change.
title: log.format
options:
- 'plain'
- 'json'
advanced: True
global: False
helpLink: logstash
path_x_logs: path_x_logs:
description: Path inside the container to wrote logs. description: Path inside the container to wrote logs.
helpLink: logstash helpLink: logstash
@@ -3,16 +3,9 @@ beacons:
postgres_pillar_beacon: postgres_pillar_beacon:
- interval: {{ AUTOAPPLY.drain_interval }} - interval: {{ AUTOAPPLY.drain_interval }}
- disable_during_state_run: False - disable_during_state_run: False
local_files_beacon: rules_beacon:
- interval: {{ AUTOAPPLY.drain_interval }} - interval: {{ AUTOAPPLY.drain_interval }}
- disable_during_state_run: False - disable_during_state_run: False
# Tags are app names in salt/reactor/pillar_push_map.yaml.
# Allowlist on purpose: salt writes elsewhere under local/salt/ and would self-retrigger.
- paths: - paths:
/opt/so/saltstack/local/salt/suricata/rules: suricata /opt/so/saltstack/local/salt/suricata/rules: suricata
/opt/so/saltstack/local/salt/strelka/rules/compiled: strelka /opt/so/saltstack/local/salt/strelka/rules/compiled: strelka
/opt/so/saltstack/local/salt/zeek/policy: zeek
/opt/so/saltstack/local/salt/zeek/zkg: zeek
/opt/so/saltstack/local/salt/elasticsearch/files/ingest: elasticsearch
/opt/so/saltstack/local/salt/elasticsearch/roles: elasticsearch
/opt/so/saltstack/local/salt/logstash/pipelines/config/custom: logstash
+8 -12
View File
@@ -113,8 +113,8 @@ manager_sbin:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://manager/tools/sbin - source: salt://manager/tools/sbin
- user: root - user: 939
- group: root - group: 939
- file_mode: 755 - file_mode: 755
- exclude_pat: - exclude_pat:
- "*_test.py" - "*_test.py"
@@ -124,8 +124,8 @@ manager_sbin_jinja:
file.recurse: file.recurse:
- name: /usr/sbin/ - name: /usr/sbin/
- source: salt://manager/tools/sbin_jinja/ - source: salt://manager/tools/sbin_jinja/
- user: root - user: socore
- group: root - group: socore
- file_mode: 755 - file_mode: 755
- template: jinja - template: jinja
- show_changes: False - show_changes: False
@@ -166,7 +166,7 @@ so-repo-sync:
so_fleetagent_status: so_fleetagent_status:
cron.present: cron.present:
- name: '/usr/sbin/so-elasticagent-status > /opt/so/log/agents/agentstatus.log.tmp 2>&1; mv -f /opt/so/log/agents/agentstatus.log.tmp /opt/so/log/agents/agentstatus.log' - name: /usr/sbin/so-elasticagent-status > /opt/so/log/agents/agentstatus.log 2>&1
- identifier: so_fleetagent_status - identifier: so_fleetagent_status
- user: root - user: root
- minute: '*/5' - minute: '*/5'
@@ -190,15 +190,11 @@ so_fleetagent_monitor:
- month: '*' - month: '*'
- dayweek: '*' - dayweek: '*'
# This tree is the source of every root-executed script (/usr/sbin, reactors, _runners, socore_own_saltstack_default:
# engines, salt-relay.sh). SOC mounts /opt/so/saltstack rw as uid 939 but only writes
# under local/. Do not add dir_mode/file_mode here -- SOC reads default/ and 750/640
# would break its config load.
root_own_saltstack_default:
file.directory: file.directory:
- name: /opt/so/saltstack/default - name: /opt/so/saltstack/default
- user: root - user: socore
- group: root - group: socore
- recurse: - recurse:
- user - user
- group - group
+8 -13
View File
@@ -106,8 +106,7 @@ while [[ $# -gt 0 ]]; do
esac esac
done done
hydraContainer=${HYDRA_CONTAINER:-so-hydra} hydraUrl=${HYDRA_URL:-http://127.0.0.1:4445}
hydraUrl=${HYDRA_URL:-http://localhost:4445}
socRolesFile=${SOC_ROLES_FILE:-/opt/so/conf/soc/soc_clients_roles} socRolesFile=${SOC_ROLES_FILE:-/opt/so/conf/soc/soc_clients_roles}
soUID=${SOCORE_UID:-939} soUID=${SOCORE_UID:-939}
soGID=${SOCORE_GID:-939} soGID=${SOCORE_GID:-939}
@@ -125,10 +124,6 @@ function fail() {
exit 1 exit 1
} }
function hydraCurl() {
docker exec "$hydraContainer" curl "$@"
}
function require() { function require() {
cmd=$1 cmd=$1
which "$1" 2>&1 > /dev/null which "$1" 2>&1 > /dev/null
@@ -138,8 +133,8 @@ function require() {
# Verify this environment is capable of running this script # Verify this environment is capable of running this script
function verifyEnvironment() { function verifyEnvironment() {
require "jq" require "jq"
require "docker" require "curl"
response=$(hydraCurl -Ss -L ${hydraUrl}/health/alive) response=$(curl -Ss -L ${hydraUrl}/health/alive)
[[ "$response" != '{"status":"ok"}' ]] && fail "Unable to communicate with Hydra; specify URL via HYDRA_URL environment variable" [[ "$response" != '{"status":"ok"}' ]] && fail "Unable to communicate with Hydra; specify URL via HYDRA_URL environment variable"
} }
@@ -169,7 +164,7 @@ function ensureRoleFileExists() {
} }
function listClients() { function listClients() {
response=$(hydraCurl -Ss -L -f ${hydraUrl}/admin/clients) response=$(curl -Ss -L -f ${hydraUrl}/admin/clients)
[[ $? != 0 ]] && fail "Unable to communicate with Hydra" [[ $? != 0 ]] && fail "Unable to communicate with Hydra"
clientIds=$(echo "${response}" | jq -r ".[] | .client_id" | sort) clientIds=$(echo "${response}" | jq -r ".[] | .client_id" | sort)
@@ -256,7 +251,7 @@ function createClient() {
EOF EOF
) )
response=$(hydraCurl -Ss -L --fail-with-body -X POST ${hydraUrl}/admin/clients -d "$body") response=$(curl -Ss -L --fail-with-body -X POST ${hydraUrl}/admin/clients -d "$body")
if [[ $? != 0 ]]; then if [[ $? != 0 ]]; then
error=$(echo $response | jq .error) error=$(echo $response | jq .error)
fail "Failed to submit request to Hydra: $error" fail "Failed to submit request to Hydra: $error"
@@ -288,7 +283,7 @@ function update() {
EOF EOF
) )
response=$(hydraCurl -Ss -L --fail-with-body -X PATCH ${hydraUrl}/admin/clients/$id -d "$body") response=$(curl -Ss -L --fail-with-body -X PATCH ${hydraUrl}/admin/clients/$id -d "$body")
if [[ $? != 0 ]]; then if [[ $? != 0 ]]; then
error=$(echo $response | jq .error) error=$(echo $response | jq .error)
fail "Failed to submit request to Hydra: $error" fail "Failed to submit request to Hydra: $error"
@@ -310,7 +305,7 @@ function generateSecret() {
EOF EOF
) )
response=$(hydraCurl -Ss -L --fail-with-body -X PATCH ${hydraUrl}/admin/clients/$id -d "$body") response=$(curl -Ss -L --fail-with-body -X PATCH ${hydraUrl}/admin/clients/$id -d "$body")
if [[ $? != 0 ]]; then if [[ $? != 0 ]]; then
error=$(echo $response | jq .error) error=$(echo $response | jq .error)
fail "Failed to submit request to Hydra: $error" fail "Failed to submit request to Hydra: $error"
@@ -322,7 +317,7 @@ function deleteClient() {
[[ ${identityId} == "" ]] && fail "Client not found" [[ ${identityId} == "" ]] && fail "Client not found"
response=$(hydraCurl -Ss -XDELETE -L --fail-with-body "${hydraUrl}/admin/clients/$identityId") response=$(curl -Ss -XDELETE -L --fail-with-body "${hydraUrl}/admin/clients/$identityId")
if [[ $? != 0 ]]; then if [[ $? != 0 ]]; then
error=$(echo $response | jq .error) error=$(echo $response | jq .error)
fail "Failed to submit request to Hydra: $error" fail "Failed to submit request to Hydra: $error"
+13 -102
View File
@@ -121,14 +121,8 @@ for i in "$@"; do
esac esac
done done
if [[ -n "$MINION_ID" && ! "$MINION_ID" =~ ^[A-Za-z0-9._-]{1,253}$ ]]; then PILLARFILE=/opt/so/saltstack/local/pillar/minions/$MINION_ID.sls
echo "Invalid minion id: $MINION_ID" ADVPILLARFILE=/opt/so/saltstack/local/pillar/minions/adv_$MINION_ID.sls
log "ERROR" "Invalid minion id: $MINION_ID"
exit 1
fi
readonly PILLARFILE=/opt/so/saltstack/local/pillar/minions/$MINION_ID.sls
readonly ADVPILLARFILE=/opt/so/saltstack/local/pillar/minions/adv_$MINION_ID.sls
function getinstallinfo() { function getinstallinfo() {
log "INFO" "Getting install info for minion $MINION_ID" log "INFO" "Getting install info for minion $MINION_ID"
@@ -139,26 +133,11 @@ function getinstallinfo() {
return 1 return 1
fi fi
# install.txt is controlled by the minion; only accept known keys and never eval or export them while read -r var; do export "$var"; done <<< "$INSTALLVARS"
local line key if [ $? -ne 0 ]; then
while IFS= read -r line; do log "ERROR" "Failed to source install variables"
[[ "$line" == *=* ]] || continue return 1
key=${line%%=*}
case "$key" in
MAINIP|MNIC|NODE_DESCRIPTION|ES_HEAP_SIZE|PATCHSCHEDULENAME|INTERFACE|NODETYPE|CORECOUNT|LSHOSTNAME|LSHEAP|CPUCORES|IDH_MGTRESTRICT|IDH_SERVICES)
printf -v "$key" '%s' "${line#*=}"
;;
*)
log "WARN" "Ignoring unexpected install var from $MINION_ID: ${key:0:64}"
;;
esac
done <<< "$INSTALLVARS"
if [[ "$NODE_DESCRIPTION" == \'*\' ]]; then
NODE_DESCRIPTION=${NODE_DESCRIPTION:1:-1}
fi fi
log "INFO" "Fetched install info for $MINION_ID (node type: ${NODETYPE:-unset})"
} }
function pcapspace() { function pcapspace() {
@@ -195,12 +174,6 @@ function pcapspace() {
fi fi
fi fi
# Must be checked before arithmetic expansion, which evaluates array subscripts
if [[ ! "$SPACESIZE" =~ ^[0-9]+$ ]]; then
log "ERROR" "Invalid disk size for $MINION_ID: ${SPACESIZE:0:64}"
return 1
fi
local s=$(( $SPACESIZE / 1000000 )) local s=$(( $SPACESIZE / 1000000 ))
local s1=$(( $s / 4 * $PCAP_PERCENTAGE )) local s1=$(( $s / 4 * $PCAP_PERCENTAGE ))
@@ -510,7 +483,6 @@ function add_sensoroni_with_analyze_to_minion() {
# Sensor settings for the minion pillar # Sensor settings for the minion pillar
function add_sensor_to_minion() { function add_sensor_to_minion() {
log "INFO" "Writing sensor configuration for $MINION_ID (interface: ${INTERFACE:-unset})"
{ {
echo "sensor:" echo "sensor:"
echo " interface: '$INTERFACE'" echo " interface: '$INTERFACE'"
@@ -537,8 +509,6 @@ function add_sensor_to_minion() {
log "ERROR" "Failed to add sensor configuration to $PILLARFILE" log "ERROR" "Failed to add sensor configuration to $PILLARFILE"
return 1 return 1
fi fi
log "INFO" "Wrote sensor configuration for $MINION_ID"
} }
function add_elastalert_to_minion() { function add_elastalert_to_minion() {
@@ -611,14 +581,11 @@ function add_telegraf_to_minion() {
# generates a password on first add and is a no-op on re-add so the cred # generates a password on first add and is a no-op on re-add so the cred
# is stable across repeated so-minion runs. postgres.telegraf_users on the # is stable across repeated so-minion runs. postgres.telegraf_users on the
# manager creates/updates the DB role from the same pillar. # manager creates/updates the DB role from the same pillar.
log "INFO" "Provisioning postgres telegraf credential for $MINION_ID" so-telegraf-cred add "$MINION_ID"
so-telegraf-cred add "$MINION_ID" if [ $? -ne 0 ]; then
local result=$? log "ERROR" "Failed to provision postgres telegraf cred for $MINION_ID"
if [ $result -ne 0 ]; then return 1
log "ERROR" "Failed to provision postgres telegraf cred for $MINION_ID (exit code: $result)" fi
return 1
fi
log "INFO" "Provisioned postgres telegraf credential for $MINION_ID"
} }
function add_influxdb_to_minion() { function add_influxdb_to_minion() {
@@ -1075,59 +1042,8 @@ function updateMineAndApplyStates() {
fi fi
} }
# Values end up in a Jinja-rendered pillar and in bash, and may come from the minion
function validate_minion_vars() {
local error_msg=""
# Inline rather than valid_ip4: so-common is not installed yet when setup runs -o=setup
local octet='(25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])'
local ip4_re="^($octet\.){3}$octet$"
case "$NODETYPE" in
EVAL|STANDALONE|MANAGER|MANAGERSEARCH|MANAGERHYPE|IMPORT)
# Manager pillars also rewrite the CA pillar, so never accept them from a remote node
[[ "$OPERATION" == "setup" ]] || error_msg="Node type $NODETYPE can only be configured during setup"
;;
FLEET|IDH|HEAVYNODE|SENSOR|SEARCHNODE|RECEIVER|HYPERVISOR|DESKTOP)
;;
*)
error_msg="Invalid node type: ${NODETYPE:0:64}"
;;
esac
if [[ -z "$error_msg" ]]; then
if [[ ! "$MAINIP" =~ $ip4_re ]]; then
error_msg="Invalid MAINIP: ${MAINIP:0:64}"
elif [[ ! "$MNIC" =~ ^[A-Za-z0-9._-]*$ ]]; then
error_msg="Invalid MNIC: ${MNIC:0:64}"
elif [[ ! "$INTERFACE" =~ ^[A-Za-z0-9._-]*$ ]]; then
error_msg="Invalid INTERFACE: ${INTERFACE:0:64}"
elif [[ ! "$LSHOSTNAME" =~ ^[A-Za-z0-9._-]*$ ]]; then
error_msg="Invalid LSHOSTNAME: ${LSHOSTNAME:0:64}"
elif [[ ! "$ES_HEAP_SIZE" =~ ^([0-9]+[kKmMgG]?)?$ ]]; then
error_msg="Invalid ES_HEAP_SIZE: ${ES_HEAP_SIZE:0:64}"
elif [[ ! "$LSHEAP" =~ ^([0-9]+[kKmMgG]?)?$ ]]; then
error_msg="Invalid LSHEAP: ${LSHEAP:0:64}"
elif [[ ! "$CORECOUNT" =~ ^[0-9]*$ ]]; then
error_msg="Invalid CORECOUNT: ${CORECOUNT:0:64}"
elif [[ ! "$CPUCORES" =~ ^[0-9]*$ ]]; then
error_msg="Invalid CPUCORES: ${CPUCORES:0:64}"
elif [[ ! "$IDH_MGTRESTRICT" =~ ^(True|False)?$ ]]; then
error_msg="Invalid IDH_MGTRESTRICT: ${IDH_MGTRESTRICT:0:64}"
fi
fi
if [[ -n "$error_msg" ]]; then
log "ERROR" "$error_msg"
echo "$error_msg"
return 1
fi
# Free text; removing braces is enough to prevent any Jinja delimiter
NODE_DESCRIPTION=${NODE_DESCRIPTION//[\{\}[:cntrl:]]/}
}
function setupMinionFiles() { function setupMinionFiles() {
log "INFO" "Setting up minion files for $MINION_ID (pillar: $PILLARFILE)" log "INFO" "Setting up minion files for $MINION_ID"
# Check to see if nodetype is set # Check to see if nodetype is set
if [ -z $NODETYPE ]; then if [ -z $NODETYPE ]; then
@@ -1137,8 +1053,6 @@ function setupMinionFiles() {
return 1 return 1
fi fi
validate_minion_vars || return 1
# Create the base minion files # Create the base minion files
create_minion_files || return 1 create_minion_files || return 1
@@ -1155,10 +1069,7 @@ function setupMinionFiles() {
fi fi
# Create node-specific configuration # Create node-specific configuration
create$NODETYPE || { create$NODETYPE || return 1
log "ERROR" "Failed to create $NODETYPE configuration for $MINION_ID"
return 1
}
# Ensure proper ownership after all content is written # Ensure proper ownership after all content is written
ensure_socore_ownership || return 1 ensure_socore_ownership || return 1
+5 -167
View File
@@ -19,10 +19,8 @@ is older than debounce_seconds, this script:
* dispatches a single `salt-run state.orchestrate orch.push_batch --async` * dispatches a single `salt-run state.orchestrate orch.push_batch --async`
with the deduped actions list passed as pillar kwargs with the deduped actions list passed as pillar kwargs
* deletes the contributed intent files on successful dispatch * deletes the contributed intent files on successful dispatch
* records the orchestration jid under /opt/so/state/push_dispatched and, on
later passes, looks up its result and logs success or per-minion failures
Reactor sls files (push_files, push_pillar) write intents Reactor sls files (push_suricata, push_strelka, push_pillar) write intents
but never dispatch directly but never dispatch directly
""" """
@@ -32,7 +30,6 @@ import json
import logging import logging
import logging.handlers import logging.handlers
import os import os
import re
import subprocess import subprocess
import sys import sys
import time import time
@@ -43,18 +40,8 @@ PENDING_DIR = '/opt/so/state/push_pending'
LOCK_FILE = os.path.join(PENDING_DIR, '.lock') LOCK_FILE = os.path.join(PENDING_DIR, '.lock')
LOG_FILE = '/opt/so/log/salt/so-push-drainer.log' LOG_FILE = '/opt/so/log/salt/so-push-drainer.log'
DISPATCHED_DIR = '/opt/so/state/push_dispatched'
HIGHSTATE_SENTINEL = '__highstate__' HIGHSTATE_SENTINEL = '__highstate__'
RESULT_CHECK_DELAY = 30
RESULT_MAX_AGE = 7200
RESULT_CHECKS_PER_PASS = 5
TEXT_LIMIT = 500
# salt-run --async reports the jid only in a log line (stderr by default).
JID_RE = re.compile(r'salt/run/(\d{20})')
def _make_logger(): def _make_logger():
logger = logging.getLogger('so-push-drainer') logger = logging.getLogger('so-push-drainer')
@@ -126,154 +113,14 @@ def _dispatch(actions, log):
except subprocess.CalledProcessError as exc: except subprocess.CalledProcessError as exc:
log.error('dispatch failed (rc=%s): stdout=%s stderr=%s', log.error('dispatch failed (rc=%s): stdout=%s stderr=%s',
exc.returncode, exc.stdout, exc.stderr) exc.returncode, exc.stdout, exc.stderr)
return None return False
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
log.error('dispatch timed out after 60s') log.error('dispatch timed out after 60s')
return None return False
except Exception: except Exception:
log.exception('dispatch raised') log.exception('dispatch raised')
return None
match = JID_RE.search('{}\n{}'.format(result.stderr or '', result.stdout or ''))
if not match:
log.warning('dispatch accepted but no jid found, result will not be tracked: stderr=%s',
_trim(result.stderr))
return ''
log.info('dispatch accepted: jid=%s', match.group(1))
return match.group(1)
def _trim(value):
text = value if isinstance(value, str) else json.dumps(value, default=str)
lines = [line.strip() for line in text.splitlines() if line.strip()]
if 'Traceback (most recent call last):' in text:
# Keep the lead-in and the raised exception; the frames are noise in a log line.
lines = [text.split('Traceback (most recent call last):', 1)[0].strip(), lines[-1]]
text = ' '.join(line for line in lines if line)
return text if len(text) <= TEXT_LIMIT else text[:TEXT_LIMIT] + '...'
def _unlink(path, log):
try:
os.unlink(path)
except OSError:
log.exception('failed to remove %s', path)
def _record_dispatch(jid, actions, paths, log):
record = {'jid': jid, 'dispatched_at': time.time(), 'actions': actions, 'paths': paths}
path = os.path.join(DISPATCHED_DIR, '{}.json'.format(jid))
try:
os.makedirs(DISPATCHED_DIR, exist_ok=True)
tmp_path = path + '.tmp'
with open(tmp_path, 'w') as f:
json.dump(record, f)
os.rename(tmp_path, path)
except OSError:
log.exception('failed to record dispatch %s', jid)
def _lookup_jid(jid, log):
"""Returns the job cache entry for jid, {} while it is still running, or None on error."""
cmd = ['salt-run', 'jobs.lookup_jid', jid, '--out=json']
try:
result = subprocess.run(cmd, check=True, capture_output=True, text=True, timeout=60)
return json.loads(result.stdout or '{}')
except (subprocess.CalledProcessError, subprocess.TimeoutExpired, ValueError) as exc:
log.warning('lookup of jid %s failed: %s', jid, exc)
return None
def _minion_failure(minion_ret):
if isinstance(minion_ret, dict):
return '; '.join(
'{}: {}'.format(state.get('__id__', state_key), _trim(state.get('comment', '')))
for state_key, state in minion_ret.items()
if isinstance(state, dict) and state.get('result') is False
)
# A state run rejected before it starts (e.g. another state run is in
# progress) returns a list of error strings instead of state results.
if isinstance(minion_ret, (list, str)):
return _trim(minion_ret)
return ''
def _step_failures(step):
if not isinstance(step, dict) or step.get('result') is not False:
return []
failures = ['{}: {}'.format(step.get('__id__', step.get('name')), _trim(step.get('comment', '')))]
changes = step.get('changes')
minion_rets = changes.get('ret') if isinstance(changes, dict) else None
if isinstance(minion_rets, dict):
for minion, minion_ret in minion_rets.items():
text = _minion_failure(minion_ret)
if text:
failures.append('{}: {}'.format(minion, text))
return failures
def _orch_failures(ret):
if not isinstance(ret, dict):
return [_trim(ret)]
failures = []
for job in ret.values():
if not isinstance(job, dict):
continue
job_ret = job.get('return')
data = job_ret.get('data') if isinstance(job_ret, dict) else {}
if not isinstance(data, dict):
if data:
failures.append(_trim(data))
data = {}
for steps in data.values():
if not isinstance(steps, dict):
failures.append(_trim(steps))
continue
for step in steps.values():
failures.extend(_step_failures(step))
if job.get('success') is False and not failures:
failures.append('orchestration reported failure: {}'.format(_trim(job.get('return'))))
return failures
def _check_dispatched(log, now):
checked = 0
for path in sorted(glob.glob(os.path.join(DISPATCHED_DIR, '*.json'))):
if checked >= RESULT_CHECKS_PER_PASS:
break
record = _read_intent(path, log)
if not isinstance(record, dict) or not record.get('jid'):
_unlink(path, log)
continue
age = now - record.get('dispatched_at', 0)
if age < RESULT_CHECK_DELAY:
continue
checked += 1
jid = record['jid']
try:
if _report_result(record, age, log):
_unlink(path, log)
except Exception:
# Drop the record so one unreadable result can't fail every pass ahead of the drain.
log.exception('cannot evaluate result for jid=%s; no longer tracking', jid)
_unlink(path, log)
def _report_result(record, age, log):
"""Logs the outcome of a dispatched push. Returns True once the record is finished with."""
jid = record['jid']
paths = record.get('paths', [])
ret = _lookup_jid(jid, log)
if not ret:
if age > RESULT_MAX_AGE:
log.warning('no result for jid=%s after %ds, no longer tracking; paths=%s', jid, age, paths)
return True
return False return False
failures = _orch_failures(ret) log.info('dispatch accepted: %s', (result.stdout or '').strip())
if failures:
log.error('push failed jid=%s paths=%s; change will be applied at the next scheduled highstate: %s',
jid, paths, ' | '.join(failures))
else:
log.info('push succeeded jid=%s paths=%s', jid, paths)
return True return True
@@ -296,9 +143,6 @@ def main():
debounce_seconds = int(push.get('debounce_seconds', 30)) debounce_seconds = int(push.get('debounce_seconds', 30))
# Outside the lock: lookups are slow and the reactors take the same lock.
_check_dispatched(log, time.time())
os.makedirs(PENDING_DIR, exist_ok=True) os.makedirs(PENDING_DIR, exist_ok=True)
lock_fd = os.open(LOCK_FILE, os.O_CREAT | os.O_RDWR, 0o644) lock_fd = os.open(LOCK_FILE, os.O_CREAT | os.O_RDWR, 0o644)
try: try:
@@ -364,16 +208,10 @@ def main():
len(ready), len(deduped), len(combined_actions), len(ready), len(deduped), len(combined_actions),
debounce_duration, all_paths[:20], debounce_duration, all_paths[:20],
) )
for action in deduped:
log.info('action: %s tgt=%s', 'highstate' if action.get('highstate') else action.get('state'),
action.get('tgt'))
jid = _dispatch(deduped, log) if not _dispatch(deduped, log):
if jid is None:
log.warning('dispatch failed; leaving intent files in place for retry') log.warning('dispatch failed; leaving intent files in place for retry')
return 1 return 1
if jid:
_record_dispatch(jid, deduped, all_paths[:20], log)
for path, _ in ready: for path, _ in ready:
try: try:
@@ -1,444 +0,0 @@
# 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.
import importlib.util
import json
import logging
import os
import shutil
import subprocess
import sys
import tempfile
import time
import unittest
from importlib.machinery import SourceFileLoader
from unittest.mock import MagicMock, patch
# salt is not installed where these tests run; the drainer only needs salt.client.Caller.
_salt = MagicMock()
sys.modules.setdefault('salt', _salt)
sys.modules.setdefault('salt.client', _salt.client)
HERE = os.path.dirname(os.path.abspath(__file__))
SCRIPT = os.path.join(HERE, 'so-push-drainer')
_loader = SourceFileLoader('so_push_drainer', SCRIPT)
_spec = importlib.util.spec_from_loader('so_push_drainer', _loader)
drainer = importlib.util.module_from_spec(_spec)
_loader.exec_module(drainer)
MASTER = 'manager.localdomain_master'
JID = '20260930171554259426'
ASYNC_STDERR = ('[WARNING ] Running in asynchronous mode. Results of this execution may be collected '
'by attaching to the master event bus or by examining the master job cache, if '
'configured. This execution is running under tag salt/run/{}\n'.format(JID))
CONFLICT = ('The function "state.sls" is running as PID 372218 and was started at '
'2026, Sep 30 17:15:40.466233 with jid 20260930171540466233')
def _orch_ret(steps, success=True):
return {MASTER: {
'fun': 'runner.state.orchestrate',
'jid': JID,
'return': {'data': {MASTER: steps}, 'outputter': 'highstate', 'retcode': 0 if success else 1},
'success': success,
}}
REFRESH_STEP = {
'salt_|-refresh_pillar_1_|-saltutil.refresh_pillar_|-function': {
'__id__': 'refresh_pillar_1', 'result': True,
'changes': {'ret': {'manager_standalone': True}},
'comment': 'Function ran successfully.',
},
}
CONFLICT_RET = _orch_ret(dict(REFRESH_STEP, **{
'salt_|-apply_soc_1_|-apply_soc_1_|-state': {
'__id__': 'apply_soc_1', 'result': False,
'changes': {'out': 'highstate', 'ret': {'manager_standalone': [CONFLICT]}},
'comment': 'Run failed on minions: manager_standalone',
},
}), success=False)
STATE_FAIL_RET = _orch_ret({
'salt_|-apply_hydra_1_|-apply_hydra_1_|-state': {
'__id__': 'apply_hydra_1', 'result': False,
'changes': {'out': 'highstate', 'ret': {'manager_standalone': {
'test_|-no_license_|-no_license_|-fail_without_changes': {
'__id__': 'hydra.enabled_no_license_detected', 'result': False,
'comment': 'This is a feature supported only for customers with a valid license.',
},
'file_|-hydra_conf_|-/opt/so/conf/hydra_|-managed': {'result': True, 'comment': 'ok'},
}}},
'comment': 'Run failed on minions: manager_standalone',
},
}, success=False)
SUCCESS_RET = _orch_ret(dict(REFRESH_STEP, **{
'salt_|-apply_telegraf_1_|-apply_telegraf_1_|-state': {
'__id__': 'apply_telegraf_1', 'result': True,
'changes': {'out': 'highstate', 'ret': {'manager_standalone': {
'file_|-tgrafconf_|-/opt/so/conf/telegraf/etc/telegraf.conf_|-managed': {'result': True},
}}},
'comment': 'States ran successfully.',
},
}))
class DrainerTestCase(unittest.TestCase):
def setUp(self):
self.tmpdir = tempfile.mkdtemp()
self.pending = os.path.join(self.tmpdir, 'push_pending')
self.dispatched = os.path.join(self.tmpdir, 'push_dispatched')
os.makedirs(self.pending)
for name, value in (
('PENDING_DIR', self.pending),
('LOCK_FILE', os.path.join(self.pending, '.lock')),
('DISPATCHED_DIR', self.dispatched),
('LOG_FILE', os.path.join(self.tmpdir, 'log', 'so-push-drainer.log')),
):
patcher = patch.object(drainer, name, value)
patcher.start()
self.addCleanup(patcher.stop)
self.log = MagicMock()
def tearDown(self):
shutil.rmtree(self.tmpdir, ignore_errors=True)
def write_json(self, directory, name, data):
os.makedirs(directory, exist_ok=True)
path = os.path.join(directory, name)
with open(path, 'w') as f:
if isinstance(data, str):
f.write(data)
else:
json.dump(data, f)
return path
def logged(self, level):
return ' '.join(c.args[0] % c.args[1:] for c in getattr(self.log, level).call_args_list)
class TestHelpers(DrainerTestCase):
def test_make_logger_adds_handler_once(self):
logger = logging.getLogger('so-push-drainer')
def close_handlers():
for handler in logger.handlers:
handler.close()
logger.handlers.clear()
self.addCleanup(close_handlers)
logger.handlers.clear()
self.assertIs(drainer._make_logger(), logger)
drainer._make_logger()
self.assertEqual(len(logger.handlers), 1)
self.assertTrue(os.path.isdir(os.path.dirname(drainer.LOG_FILE)))
def test_load_push_cfg(self):
with patch.object(drainer.salt.client, 'Caller') as caller:
caller.return_value.cmd.return_value = {'enabled': False}
self.assertEqual(drainer._load_push_cfg(), {'enabled': False})
caller.return_value.cmd.return_value = 'garbage'
self.assertEqual(drainer._load_push_cfg(), {})
def test_read_intent(self):
good = self.write_json(self.pending, 'good.json', {'a': 1})
bad = self.write_json(self.pending, 'bad.json', '{nope')
self.assertEqual(drainer._read_intent(good, self.log), {'a': 1})
self.assertIsNone(drainer._read_intent(bad, self.log))
with patch('builtins.open', side_effect=RuntimeError('boom')):
self.assertIsNone(drainer._read_intent(good, self.log))
self.log.exception.assert_called_once()
def test_dedupe_actions(self):
actions = [
'not a dict',
{'state': 'soc'},
{'state': 'soc', 'tgt': '*'},
{'state': 'soc', 'tgt': '*', 'tgt_type': 'compound'},
{'highstate': True, 'tgt': '*'},
{'state': 'soc', 'tgt': 'node1', 'tgt_type': 'glob'},
]
self.assertEqual(drainer._dedupe_actions(actions), [actions[2], actions[4], actions[5]])
def test_trim(self):
self.assertEqual(drainer._trim(' text \n'), 'text')
self.assertEqual(drainer._trim(['a']), '["a"]')
self.assertEqual(drainer._trim(None), 'null')
self.assertEqual(drainer._trim('x' * 600), 'x' * drainer.TEXT_LIMIT + '...')
def test_trim_traceback(self):
comment = ('An exception occurred in this state: Traceback (most recent call last):\n'
' File "salt/client/__init__.py", line 1934, in pub\n'
' raise AuthenticationError(err_msg)\n'
'salt.exceptions.AuthenticationError: Authentication error occurred.\n')
self.assertEqual(drainer._trim(comment), 'An exception occurred in this state: '
'salt.exceptions.AuthenticationError: Authentication error occurred.')
self.assertEqual(drainer._trim('line one\n line two\n'), 'line one line two')
def test_unlink_missing_logs(self):
drainer._unlink(os.path.join(self.tmpdir, 'missing'), self.log)
self.log.exception.assert_called_once()
class TestDispatch(DrainerTestCase):
def run_dispatch(self, **kwargs):
with patch.object(drainer.subprocess, 'run', **kwargs) as run:
jid = drainer._dispatch([{'state': 'soc', 'tgt': '*'}], self.log)
return jid, run
def test_jid_parsed_from_stderr(self):
jid, run = self.run_dispatch(return_value=MagicMock(stdout='', stderr=ASYNC_STDERR))
self.assertEqual(jid, JID)
cmd = run.call_args[0][0]
self.assertEqual(cmd[:3], ['salt-run', 'state.orchestrate', 'orch.push_batch'])
self.assertIn('--async', cmd)
def test_jid_parsed_from_stdout(self):
jid, _ = self.run_dispatch(return_value=MagicMock(stdout=ASYNC_STDERR, stderr=None))
self.assertEqual(jid, JID)
def test_no_jid(self):
jid, _ = self.run_dispatch(return_value=MagicMock(stdout='', stderr=None))
self.assertEqual(jid, '')
self.log.warning.assert_called_once()
def test_failures_return_none(self):
for exc in (subprocess.CalledProcessError(1, 'salt-run', 'out', 'err'),
subprocess.TimeoutExpired('salt-run', 60),
RuntimeError('boom')):
jid, _ = self.run_dispatch(side_effect=exc)
self.assertIsNone(jid)
def test_record_dispatch(self):
drainer._record_dispatch(JID, [{'state': 'soc'}], ['audit:soc.config.licenseKey'], self.log)
with open(os.path.join(self.dispatched, JID + '.json')) as f:
record = json.load(f)
self.assertEqual(record['jid'], JID)
self.assertEqual(record['paths'], ['audit:soc.config.licenseKey'])
self.assertIn('dispatched_at', record)
def test_record_dispatch_oserror(self):
with patch.object(drainer.os, 'makedirs', side_effect=OSError('ro')):
drainer._record_dispatch(JID, [], [], self.log)
self.log.exception.assert_called_once()
class TestResults(DrainerTestCase):
def test_lookup_jid(self):
with patch.object(drainer.subprocess, 'run') as run:
run.return_value = MagicMock(stdout=json.dumps(SUCCESS_RET))
self.assertEqual(drainer._lookup_jid(JID, self.log), SUCCESS_RET)
self.assertEqual(run.call_args[0][0], ['salt-run', 'jobs.lookup_jid', JID, '--out=json'])
run.return_value = MagicMock(stdout='')
self.assertEqual(drainer._lookup_jid(JID, self.log), {})
run.return_value = MagicMock(stdout='not json')
self.assertIsNone(drainer._lookup_jid(JID, self.log))
run.side_effect = subprocess.TimeoutExpired('salt-run', 60)
self.assertIsNone(drainer._lookup_jid(JID, self.log))
def test_minion_failure_shapes(self):
self.assertEqual(drainer._minion_failure([CONFLICT]), json.dumps([CONFLICT]))
self.assertEqual(drainer._minion_failure('Rendering SLS failed'), 'Rendering SLS failed')
self.assertEqual(drainer._minion_failure(True), '')
self.assertEqual(drainer._minion_failure({'a': {'result': True}}), '')
def test_orch_failures_conflict(self):
failures = drainer._orch_failures(CONFLICT_RET)
self.assertEqual(failures[0], 'apply_soc_1: Run failed on minions: manager_standalone')
self.assertIn('manager_standalone', failures[1])
self.assertIn('is running as PID 372218', failures[1])
self.assertEqual(len(failures), 2)
def test_orch_failures_failed_state(self):
failures = drainer._orch_failures(STATE_FAIL_RET)
self.assertEqual(len(failures), 2)
self.assertIn('hydra.enabled_no_license_detected: This is a feature', failures[1])
self.assertNotIn('hydra_conf', failures[1])
def test_orch_failures_success(self):
self.assertEqual(drainer._orch_failures(SUCCESS_RET), [])
def test_orch_failures_render_error(self):
ret = {MASTER: {'return': {'data': {MASTER: ['Rendering SLS failed']}}, 'success': False}}
self.assertEqual(drainer._orch_failures(ret), ['["Rendering SLS failed"]'])
def test_orch_failures_not_a_dict(self):
self.assertEqual(drainer._orch_failures(['No minions matched']), ['["No minions matched"]'])
self.assertEqual(drainer._orch_failures('Runner error'), ['Runner error'])
def test_orch_failures_data_not_a_dict(self):
ret = {MASTER: {'return': {'data': ["Rendering SLS 'orch.push_batch' failed"]}, 'success': False}}
self.assertEqual(drainer._orch_failures(ret), ['["Rendering SLS \'orch.push_batch\' failed"]'])
def test_orch_failures_odd_changes(self):
for changes in ('Run failed', {'ret': ['manager_standalone']}):
ret = _orch_ret({'salt_|-apply_soc_1_|-apply_soc_1_|-state': {
'__id__': 'apply_soc_1', 'result': False, 'changes': changes, 'comment': 'Run failed on minions',
}}, success=False)
self.assertEqual(drainer._orch_failures(ret), ['apply_soc_1: Run failed on minions'])
def test_orch_failures_unparsed(self):
self.assertEqual(drainer._orch_failures({MASTER: 'odd'}), [])
ret = {MASTER: {'return': 'Exception occurred', 'success': False}}
self.assertEqual(drainer._orch_failures(ret), ['orchestration reported failure: Exception occurred'])
def record(self, jid, age, now):
return self.write_json(self.dispatched, jid + '.json', {
'jid': jid, 'dispatched_at': now - age, 'actions': [], 'paths': ['audit:' + jid],
})
def test_check_dispatched(self):
now = time.time()
results = {
'1_failed': CONFLICT_RET,
'2_ok': SUCCESS_RET,
'3_pending': {},
'4_expired': None,
}
young = self.record('0_young', 5, now)
paths = {jid: self.record(jid, 60, now) for jid in results}
paths['4_expired'] = self.record('4_expired', drainer.RESULT_MAX_AGE + 1, now)
bad = self.write_json(self.dispatched, '5_bad.json', '{nope')
with patch.object(drainer, '_lookup_jid', side_effect=lambda jid, log: results[jid]):
drainer._check_dispatched(self.log, now)
self.assertTrue(os.path.exists(young))
self.assertTrue(os.path.exists(paths['3_pending']))
for jid in ('1_failed', '2_ok', '4_expired'):
self.assertFalse(os.path.exists(paths[jid]), jid)
self.assertFalse(os.path.exists(bad))
self.assertIn('push failed jid=1_failed', self.logged('error'))
self.assertIn('is running as PID 372218', self.logged('error'))
self.assertIn('push succeeded jid=2_ok', self.logged('info'))
self.assertIn('no result for jid=4_expired', self.logged('warning'))
def test_check_dispatched_survives_bad_result(self):
now = time.time()
bad = self.record('1_bad', 60, now)
good = self.record('2_ok', 60, now)
def orch_failures(ret):
if ret == 'boom':
raise ValueError('unexpected shape')
return []
with patch.object(drainer, '_lookup_jid', side_effect=lambda jid, log: 'boom' if jid == '1_bad' else SUCCESS_RET), \
patch.object(drainer, '_orch_failures', side_effect=orch_failures):
drainer._check_dispatched(self.log, now)
self.assertFalse(os.path.exists(bad))
self.assertFalse(os.path.exists(good))
self.log.exception.assert_called_once()
self.assertIn('jid=1_bad', self.log.exception.call_args[0][0] % self.log.exception.call_args[0][1:])
self.assertIn('push succeeded jid=2_ok', self.logged('info'))
def test_check_dispatched_limit(self):
now = time.time()
for i in range(drainer.RESULT_CHECKS_PER_PASS + 2):
self.record('{:02d}'.format(i), 60, now)
with patch.object(drainer, '_lookup_jid', return_value={}) as lookup:
drainer._check_dispatched(self.log, now)
self.assertEqual(lookup.call_count, drainer.RESULT_CHECKS_PER_PASS)
class TestMain(DrainerTestCase):
def setUp(self):
super().setUp()
self.cfg = {'enabled': True, 'debounce_seconds': 30}
for name, kwargs in (
('_make_logger', {'return_value': self.log}),
('_load_push_cfg', {'side_effect': lambda: self.cfg}),
('_check_dispatched', {}),
):
patcher = patch.object(drainer, name, **kwargs)
setattr(self, name, patcher.start())
self.addCleanup(patcher.stop)
def intent(self, name, age=60, actions=None, paths=None):
now = time.time()
return self.write_json(self.pending, name, {
'first_touch': now - age - 5, 'last_touch': now - age,
'actions': [{'state': 'soc', 'tgt': '*'}] if actions is None else actions,
'paths': paths or ['audit:soc.config.licenseKey'],
})
def test_no_pending_dir(self):
shutil.rmtree(self.pending)
self.assertEqual(drainer.main(), 0)
self._load_push_cfg.assert_not_called()
def test_cfg_error(self):
self._load_push_cfg.side_effect = RuntimeError('no salt')
self.assertEqual(drainer.main(), 1)
def test_disabled(self):
self.cfg['enabled'] = False
self.assertEqual(drainer.main(), 0)
self._check_dispatched.assert_not_called()
def test_no_intents_still_checks_results(self):
self.assertEqual(drainer.main(), 0)
self._check_dispatched.assert_called_once()
def test_debounce_and_broken(self):
young = self.intent('young.json', age=1)
broken = self.write_json(self.pending, 'broken.json', '{nope')
with patch.object(drainer, '_dispatch') as dispatch:
self.assertEqual(drainer.main(), 0)
dispatch.assert_not_called()
self.assertTrue(os.path.exists(young))
self.assertFalse(os.path.exists(broken))
def test_broken_unlink_error_ignored(self):
self.write_json(self.pending, 'broken.json', '{nope')
with patch.object(drainer.os, 'unlink', side_effect=OSError('busy')):
self.assertEqual(drainer.main(), 0)
def test_no_usable_actions(self):
path = self.intent('empty.json', actions=[{'state': 'soc'}])
self.assertEqual(drainer.main(), 0)
self.assertFalse(os.path.exists(path))
self.intent('empty.json', actions=[{'state': 'soc'}])
with patch.object(drainer.os, 'unlink', side_effect=OSError('busy')):
self.assertEqual(drainer.main(), 0)
def test_dispatch_failure_keeps_intents(self):
path = self.intent('pillar_soc.json')
with patch.object(drainer, '_dispatch', return_value=None):
self.assertEqual(drainer.main(), 1)
self.assertTrue(os.path.exists(path))
def test_dispatch_records_jid(self):
soc = self.intent('pillar_soc.json')
hs = self.intent('pillar_global.json', actions=[{'highstate': True, 'tgt': '*'}], paths=['audit:global.x'])
with patch.object(drainer, '_dispatch', return_value=JID) as dispatch, \
patch.object(drainer, '_record_dispatch') as record:
self.assertEqual(drainer.main(), 0)
self.assertEqual(len(dispatch.call_args[0][0]), 2)
record.assert_called_once()
self.assertEqual(record.call_args[0][0], JID)
self.assertEqual(sorted(record.call_args[0][2]), ['audit:global.x', 'audit:soc.config.licenseKey'])
self.assertFalse(os.path.exists(soc))
self.assertFalse(os.path.exists(hs))
self.assertIn('action: highstate tgt=*', self.logged('info'))
def test_dispatch_without_jid_not_recorded(self):
self.intent('pillar_soc.json')
with patch.object(drainer, '_dispatch', return_value=''), \
patch.object(drainer, '_record_dispatch') as record, \
patch.object(drainer.os, 'unlink', side_effect=OSError('busy')):
self.assertEqual(drainer.main(), 0)
record.assert_not_called()
self.log.exception.assert_called_once()
if __name__ == '__main__':
unittest.main()
+2 -2
View File
@@ -124,8 +124,8 @@ copy_new_files() {
rsync -a salt $default_salt_dir/ rsync -a salt $default_salt_dir/
rsync -a pillar $default_salt_dir/ rsync -a pillar $default_salt_dir/
chown -R root:root $default_salt_dir/salt chown -R socore:socore $default_salt_dir/salt
chown -R root:root $default_salt_dir/pillar chown -R socore:socore $default_salt_dir/pillar
chmod 755 $default_salt_dir/pillar/firewall/addfirewall.sh chmod 755 $default_salt_dir/pillar/firewall/addfirewall.sh
rm -rf /tmp/sogh rm -rf /tmp/sogh
@@ -1,219 +0,0 @@
#!/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-soup-grid-highstate
# ======================
# Drives a batched, role-tiered highstate across every non-manager minion in the
# grid. soup fires this (detached) after it finishes upgrading the manager so the
# rest of the grid converges immediately instead of waiting for its own scheduled
# highstate -- which, since the schedule moved from 15 minutes to 120 minutes
# (salt:schedule:highstate_interval_minutes), could otherwise leave nodes on the
# old version for up to ~2.5 hours (interval + splay) while the manager runs new code.
#
# Work is done by the existing orch.push_batch orchestration (salt/orch/push_batch.sls),
# the same runner the active-push drainer uses, so batching/queueing behavior matches.
# Tiers are dispatched in declaration order: searchnodes/heavynodes (Elasticsearch data
# nodes) first, then receivers, then everything else -- so the data tier converges before
# the ingest tier before sensors/fleet/idh/etc.
#
# When soup also upgraded Salt itself, remote minions must first highstate onto the new
# salt-minion package (top.sls gates every real state on G@saltversion, so a stale-version
# minion only gets salt.minion until it upgrades and reconnects). --salt-upgraded runs that
# preliminary pass and waits for the fleet to settle before the tiered pass.
#
# This is best-effort: soup has already completed by the time this runs, and the 120-minute
# scheduled highstate remains the backstop for any node that is offline or missed a batch.
LOG_FILE=/opt/so/log/salt/so-soup-grid-highstate
LOCK_FILE=/opt/so/state/so-soup-grid-highstate.lock
SETTLE_MAX_WAIT=${GRID_HIGHSTATE_SETTLE_WAIT:-900} # backstop for the post-salt-upgrade settle loop
SETTLE_INTERVAL=15
SETTLE_STABLE_CHECKS=3
# salt-minion on an upgraded node restarts ~30s after the upgrade state runs
# (salt/salt/minion/init.sls start_minion_post_upgrade); wait past that before sampling
# so the settle loop sees the drop-off instead of settling on the pre-restart set.
SETTLE_INITIAL_WAIT=${GRID_HIGHSTATE_SETTLE_INITIAL_WAIT:-45}
BATCH=""
BATCH_WAIT=""
SALT_UPGRADED=false
REASON="manual"
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') | $*" | tee -a "$LOG_FILE"
}
usage() {
echo "Usage: so-soup-grid-highstate [--batch <spec>] [--batch-wait <sec>] [--salt-upgraded] [--reason <text>]"
exit 1
}
while [ $# -gt 0 ]; do
case "$1" in
--batch) BATCH="$2"; shift 2 ;;
--batch-wait) BATCH_WAIT="$2"; shift 2 ;;
--salt-upgraded) SALT_UPGRADED=true; shift ;;
--reason) REASON="$2"; shift 2 ;;
-h|--help) usage ;;
*) echo "Unknown option: $1"; usage ;;
esac
done
mkdir -p "$(dirname "$LOG_FILE")" "$(dirname "$LOCK_FILE")"
# Serialize: a second invocation (e.g. two soups, or a manual run overlapping soup's)
# should not dispatch a competing set of batches.
exec 9>"$LOCK_FILE"
if ! flock -n 9; then
log "another so-soup-grid-highstate is already running (lock $LOCK_FILE held); exiting"
exit 0
fi
# Resolve batch settings from the salt:auto_apply pillar when not overridden on the
# command line, falling back to the same defaults orch.push_batch/salt.defaults use.
if [ -z "$BATCH" ]; then
BATCH=$(salt-call --out=newline_values_only pillar.get salt:auto_apply:batch 2>/dev/null)
[ -z "$BATCH" ] && BATCH='10%'
fi
if [ -z "$BATCH_WAIT" ]; then
BATCH_WAIT=$(salt-call --out=newline_values_only pillar.get salt:auto_apply:batch_wait 2>/dev/null)
[ -z "$BATCH_WAIT" ] && BATCH_WAIT=15
fi
MINIONID=$(salt-call --local --out=newline_values_only grains.get id 2>/dev/null)
[ -z "$MINIONID" ] && MINIONID=$(cat /etc/salt/minion_id 2>/dev/null)
if [ -z "$MINIONID" ]; then
log "could not determine this minion's id; aborting"
exit 1
fi
# Single-node grids (eval/standalone/import with no other accepted keys) have nothing
# remote to push -- the manager already highstated during soup.
NUM_ACCEPTED=$(salt-key --out=json --list=accepted 2>/dev/null | jq -r '.minions | length' 2>/dev/null)
NUM_ACCEPTED=${NUM_ACCEPTED:-0}
if [ "$NUM_ACCEPTED" -le 1 ]; then
log "single node grid ($NUM_ACCEPTED accepted minion(s)); nothing to push (reason=$REASON)"
exit 0
fi
log "starting grid highstate: reason=$REASON minion=$MINIONID accepted=$NUM_ACCEPTED batch=$BATCH batch_wait=$BATCH_WAIT salt_upgraded=$SALT_UPGRADED"
# Count minions currently responsive on the bus (includes this manager).
count_up() {
salt-run manage.up --out=json 2>/dev/null \
| python3 -c 'import sys,json; print(len(json.load(sys.stdin)))' 2>/dev/null
}
# Dispatch a single synchronous orch.push_batch run for the given actions JSON.
# Synchronous is fine: soup launched us detached, so blocking here does not hold soup up.
# expect_restart=true marks a dispatch (the salt-upgrade pass) where a non-zero rc is normal
# because targets restart salt-minion mid-run -- so we don't log a misleading failure warning.
dispatch() {
local desc="$1"
local actions="$2"
local expect_restart="${3:-false}"
local rc
log "dispatching $desc"
salt-run state.orchestrate orch.push_batch pillar="{\"actions\": $actions}" >>"$LOG_FILE" 2>&1
rc=$?
if [ "$rc" -eq 0 ]; then
log "$desc dispatch completed (rc=0)"
elif [ "$expect_restart" = "true" ]; then
log "$desc returned rc=$rc; this is expected during a salt upgrade (targets restart salt-minion mid-run). Waiting for them to reconnect before the tiered pass."
else
log "WARNING: $desc dispatch returned rc=$rc; nodes it missed will converge on the scheduled highstate"
fi
}
# Wait for the reachable minion set to recover to its pre-upgrade size and hold steady.
# Used after the salt-upgrade pass, where targets restart salt-minion (~30s delayed, see
# salt/salt/minion/init.sls) and drop off the bus before reconnecting on the new version.
# target = how many minions were reachable just before the pass; requiring up >= target keeps
# us from releasing the tiered pass while nodes are still down for their restart (settling on
# the not-yet-restarted subset). We deliberately compare against the pre-upgrade reachable
# count, not accepted keys, so a node an operator intentionally powered off never stalls us.
# Bounded by SETTLE_MAX_WAIT.
wait_for_settle() {
local target="$1"
local elapsed=0 prev=-1 stable=0 up=0
# Let the delayed salt-minion restart begin before we start counting stability, otherwise
# we could see the pre-restart set as "stable" and settle before the drop-off even happens.
sleep "$SETTLE_INITIAL_WAIT"
elapsed=$SETTLE_INITIAL_WAIT
while [ "$elapsed" -lt "$SETTLE_MAX_WAIT" ]; do
up=$(count_up); up=${up:-0}
if [ "$up" -ge "$target" ] && [ "$up" -eq "$prev" ]; then
stable=$((stable + 1))
[ "$stable" -ge "$SETTLE_STABLE_CHECKS" ] && break
else
stable=0
fi
prev=$up
sleep "$SETTLE_INTERVAL"
elapsed=$((elapsed + SETTLE_INTERVAL))
done
if [ "$up" -ge "$target" ]; then
log "fleet recovered to ${up} minions up (>= pre-upgrade ${target}) after ${elapsed}s"
else
log "WARNING: ${SETTLE_MAX_WAIT}s settle backstop hit; only ${up}/${target} pre-upgrade minions back up; proceeding (stragglers converge on the scheduled highstate)"
fi
}
# Pass 0: when Salt itself was upgraded, remote minions still on the old version only match
# top.sls's 'not G@saltversion' block (salt.minion, which performs the package upgrade). Push
# an untiered highstate so they upgrade+reconnect, then wait for them to come back before the
# real tiered pass applies the new version's states.
if [ "$SALT_UPGRADED" = "true" ]; then
PRE_UP=$(count_up); PRE_UP=${PRE_UP:-1}
log "pre-upgrade reachable minions (incl. this manager): $PRE_UP"
dispatch "salt-upgrade pass (all remote minions)" \
"[{\"highstate\": true, \"tgt\": \"not $MINIONID\", \"tgt_type\": \"compound\", \"batch\": \"$BATCH\", \"batch_wait\": $BATCH_WAIT}]" \
true
log "waiting for minions to reconnect on the new salt version"
wait_for_settle "$PRE_UP"
fi
# Tiered pass: Elasticsearch data nodes first, then receivers, then the remainder. The last
# tier is defined as the complement of the earlier tiers (and of this manager) so coverage is
# exhaustive -- sensors, fleet, idh, desktop, hypervisor, and any future role are all included.
TIER_TGTS=(
"( *_searchnode or *_heavynode ) and not $MINIONID"
"*_receiver and not $MINIONID"
"not $MINIONID and not *_searchnode and not *_heavynode and not *_receiver"
)
# Count minions a compound target matches, using the master's key/cache data (no execution).
tier_count() {
salt --out=json -C "$1" --preview-target 2>/dev/null | jq 'length' 2>/dev/null
}
# Build the actions JSON, including only tiers that actually match minions. An empty target
# would make orch.push_batch's salt.state step return "No minions returned" -- a failure --
# even though nothing needed to run, and grids commonly lack a tier (no receiver, etc.).
# Keep the JSON on a single line: salt parses `pillar=<value>` kwargs with a non-DOTALL
# regex, so an embedded newline makes it treat the whole token as a positional saltenv
# instead ("No matching salt environment for environment 'pillar=...'").
actions=""
for tgt in "${TIER_TGTS[@]}"; do
n=$(tier_count "$tgt"); n=${n:-0}
if [ "$n" -ge 1 ]; then
[ -n "$actions" ] && actions="$actions, "
actions="$actions{\"highstate\": true, \"tgt\": \"$tgt\", \"tgt_type\": \"compound\", \"batch\": \"$BATCH\", \"batch_wait\": $BATCH_WAIT}"
log "tier matched $n minion(s): $tgt"
else
log "tier matched 0 minions, skipping: $tgt"
fi
done
if [ -z "$actions" ]; then
log "no remote minions matched any tier; nothing to push (reason=$REASON)"
exit 0
fi
dispatch "tiered pass (searchnodes/heavynodes -> receivers -> remainder)" "[$actions]"
log "grid highstate complete (reason=$REASON)"
exit 0
+11 -16
View File
@@ -129,8 +129,7 @@ while [[ $# -gt 0 ]]; do
esac esac
done done
kratosContainer=${KRATOS_CONTAINER:-so-kratos} kratosUrl=${KRATOS_URL:-http://127.0.0.1:4434/admin}
kratosUrl=${KRATOS_URL:-http://localhost:4434/admin}
databasePath=${KRATOS_DB_PATH:-/nsm/kratos/db/db.sqlite} databasePath=${KRATOS_DB_PATH:-/nsm/kratos/db/db.sqlite}
databaseTimeout=${KRATOS_DB_TIMEOUT:-5000} databaseTimeout=${KRATOS_DB_TIMEOUT:-5000}
bcryptRounds=${BCRYPT_ROUNDS:-12} bcryptRounds=${BCRYPT_ROUNDS:-12}
@@ -155,10 +154,6 @@ function fail() {
exit 1 exit 1
} }
function kratosCurl() {
docker exec "$kratosContainer" curl "$@"
}
function require() { function require() {
cmd=$1 cmd=$1
which "$1" 2>&1 > /dev/null which "$1" 2>&1 > /dev/null
@@ -169,18 +164,18 @@ function require() {
function verifyEnvironment() { function verifyEnvironment() {
require "htpasswd" require "htpasswd"
require "jq" require "jq"
require "docker" require "curl"
require "openssl" require "openssl"
require "sqlite3" require "sqlite3"
[[ ! -f $databasePath ]] && fail "Unable to find database file; specify path via KRATOS_DB_PATH environment variable" [[ ! -f $databasePath ]] && fail "Unable to find database file; specify path via KRATOS_DB_PATH environment variable"
response=$(kratosCurl -Ss -L ${kratosUrl}/) response=$(curl -Ss -L ${kratosUrl}/)
[[ "$response" != "404 page not found" ]] && fail "Unable to communicate with Kratos; specify URL via KRATOS_URL environment variable" [[ "$response" != "404 page not found" ]] && fail "Unable to communicate with Kratos; specify URL via KRATOS_URL environment variable"
} }
function findIdByEmail() { function findIdByEmail() {
email=${1,,} email=${1,,}
response=$(kratosCurl -Ss -L ${kratosUrl}/identities) response=$(curl -Ss -L ${kratosUrl}/identities)
identityId=$(echo "${response}" | jq -r ".[] | select(.verifiable_addresses[0].value == \"$email\") | .id") identityId=$(echo "${response}" | jq -r ".[] | select(.verifiable_addresses[0].value == \"$email\") | .id")
echo $identityId echo $identityId
} }
@@ -421,7 +416,7 @@ function syncAll() {
} }
function listUsers() { function listUsers() {
response=$(kratosCurl -Ss -L ${kratosUrl}/identities) response=$(curl -Ss -L ${kratosUrl}/identities)
[[ $? != 0 ]] && fail "Unable to communicate with Kratos" [[ $? != 0 ]] && fail "Unable to communicate with Kratos"
users=$(echo "${response}" | jq -r ".[] | .verifiable_addresses[0].value" | sort) users=$(echo "${response}" | jq -r ".[] | .verifiable_addresses[0].value" | sort)
@@ -500,7 +495,7 @@ function createUser() {
EOF EOF
) )
response=$(kratosCurl -Ss -L ${kratosUrl}/identities -d "$addUserJson") response=$(curl -Ss -L ${kratosUrl}/identities -d "$addUserJson")
[[ $? != 0 ]] && fail "Unable to communicate with Kratos" [[ $? != 0 ]] && fail "Unable to communicate with Kratos"
identityId=$(echo "${response}" | jq -r ".id") identityId=$(echo "${response}" | jq -r ".id")
@@ -523,7 +518,7 @@ function updateStatus() {
identityId=$(findIdByEmail "$email") identityId=$(findIdByEmail "$email")
[[ ${identityId} == "" ]] && fail "User not found" [[ ${identityId} == "" ]] && fail "User not found"
response=$(kratosCurl -Ss -L "${kratosUrl}/identities/$identityId") response=$(curl -Ss -L "${kratosUrl}/identities/$identityId")
[[ $? != 0 ]] && fail "Unable to communicate with Kratos" [[ $? != 0 ]] && fail "Unable to communicate with Kratos"
schemaId=$(echo "$response" | jq -r .schema_id) schemaId=$(echo "$response" | jq -r .schema_id)
@@ -536,7 +531,7 @@ function updateStatus() {
state="inactive" state="inactive"
fi fi
body="{ \"schema_id\": \"$schemaId\", \"state\": \"$state\", \"traits\": $traitBlock }" body="{ \"schema_id\": \"$schemaId\", \"state\": \"$state\", \"traits\": $traitBlock }"
response=$(kratosCurl -fSsL -XPUT -H "Content-Type: application/json" "${kratosUrl}/identities/$identityId" -d "$body") response=$(curl -fSsL -XPUT -H "Content-Type: application/json" "${kratosUrl}/identities/$identityId" -d "$body")
[[ $? != 0 ]] && fail "Unable to update user" [[ $? != 0 ]] && fail "Unable to update user"
} }
@@ -555,7 +550,7 @@ function updateUserProfile() {
identityId=$(findIdByEmail "$email") identityId=$(findIdByEmail "$email")
[[ ${identityId} == "" ]] && fail "User not found" [[ ${identityId} == "" ]] && fail "User not found"
response=$(kratosCurl -Ss -L "${kratosUrl}/identities/$identityId") response=$(curl -Ss -L "${kratosUrl}/identities/$identityId")
[[ $? != 0 ]] && fail "Unable to communicate with Kratos" [[ $? != 0 ]] && fail "Unable to communicate with Kratos"
schemaId=$(echo "$response" | jq -r .schema_id) schemaId=$(echo "$response" | jq -r .schema_id)
@@ -564,7 +559,7 @@ function updateUserProfile() {
traitBlock="{\"email\":\"$email\",\"firstName\":\"$firstName\",\"lastName\":\"$lastName\",\"note\":\"$note\"}" traitBlock="{\"email\":\"$email\",\"firstName\":\"$firstName\",\"lastName\":\"$lastName\",\"note\":\"$note\"}"
body="{ \"schema_id\": \"$schemaId\", \"state\": \"$state\", \"traits\": $traitBlock }" body="{ \"schema_id\": \"$schemaId\", \"state\": \"$state\", \"traits\": $traitBlock }"
response=$(kratosCurl -fSsL -XPUT -H "Content-Type: application/json" "${kratosUrl}/identities/$identityId" -d "$body") response=$(curl -fSsL -XPUT -H "Content-Type: application/json" "${kratosUrl}/identities/$identityId" -d "$body")
[[ $? != 0 ]] && fail "Unable to update user" [[ $? != 0 ]] && fail "Unable to update user"
} }
@@ -574,7 +569,7 @@ function deleteUser() {
identityId=$(findIdByEmail "$email") identityId=$(findIdByEmail "$email")
[[ ${identityId} == "" ]] && fail "User not found" [[ ${identityId} == "" ]] && fail "User not found"
response=$(kratosCurl -Ss -XDELETE -L "${kratosUrl}/identities/$identityId") response=$(curl -Ss -XDELETE -L "${kratosUrl}/identities/$identityId")
[[ $? != 0 ]] && fail "Unable to communicate with Kratos" [[ $? != 0 ]] && fail "Unable to communicate with Kratos"
rolesTmpFile="${socRolesFile}.tmp" rolesTmpFile="${socRolesFile}.tmp"
+30 -326
View File
@@ -24,11 +24,7 @@ else
POSTVERSION=$INSTALLEDVERSION POSTVERSION=$INSTALLEDVERSION
fi fi
INSTALLEDSALTVERSION=$(salt --versions-report | grep Salt: | awk '{print $2}') INSTALLEDSALTVERSION=$(salt --versions-report | grep Salt: | awk '{print $2}')
# Optional -b override for the grid highstate batch size (a count like "5" or a BATCHSIZE=5
# percentage like "25%"). Empty means so-soup-grid-highstate uses the salt:auto_apply:batch
# pillar default.
BATCHSIZE=
DEFAULT_DOCKER_RANGE='172.17.1.0/24'
SOUP_LOG=/root/soup.log SOUP_LOG=/root/soup.log
SOUP_DEBUG_LOG=/root/soup-debug.log SOUP_DEBUG_LOG=/root/soup-debug.log
WHATWOULDYOUSAYYAHDOHERE=soup WHATWOULDYOUSAYYAHDOHERE=soup
@@ -121,9 +117,6 @@ check_err() {
161) 161)
echo 'Required intermediate Elasticsearch upgrade not complete' echo 'Required intermediate Elasticsearch upgrade not complete'
;; ;;
162)
echo 'One or more Elastic Agent nodes do not support the x86-64-v3 CPU instruction set'
;;
170) 170)
echo "Intermediate upgrade completed successfully to $next_step_so_version, but next soup to Security Onion $originally_requested_so_version could not be started automatically." echo "Intermediate upgrade completed successfully to $next_step_so_version, but next soup to Security Onion $originally_requested_so_version could not be started automatically."
echo "Start soup again manually to continue the upgrade to Security Onion $originally_requested_so_version." echo "Start soup again manually to continue the upgrade to Security Onion $originally_requested_so_version."
@@ -335,96 +328,14 @@ check_pillar_items() {
} }
check_cluster_health() { check_cluster_health() {
# Require a 'green' cluster before upgrading
echo "Checking Elasticsearch cluster health." echo "Checking Elasticsearch cluster health."
# Require a 'green' cluster before upgrading; anything less (yellow, red, or
# unreachable) blocks. Modeled on the wait used in so-elasticsearch-roles-load.
if so-elasticsearch-query "_cluster/health?wait_for_status=green&timeout=120s" --fail > /dev/null 2>&1; then if so-elasticsearch-query "_cluster/health?wait_for_status=green&timeout=120s" --fail > /dev/null 2>&1; then
printf "\nThe Elasticsearch cluster is healthy (green). We can proceed with SOUP.\n\n" printf "\nThe Elasticsearch cluster is healthy (green). We can proceed with SOUP.\n\n"
return
fi
if command -v so-elasticsearch-troubleshoot > /dev/null 2>&1; then
printf "\nRunning so-elasticsearch-troubleshoot for additional detail.\n"
so-elasticsearch-troubleshoot || true
fi
printf "\nThe Elasticsearch cluster is not green. Resolve the cluster health issue so the cluster is green before running SOUP again.\n\n"
exit 0
}
no_soup_for_you() {
echo ""
echo "No soup for you!"
exit 162
}
check_cpu_compatibility() {
# Roles running a container built from the so-elastic-agent image; mirrors the
# elasticagent and elasticfleet entries in salt/reactor/pillar_push_map.yaml.
local cpu_target='G@role:so-heavynode or G@role:so-eval or G@role:so-fleet 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'
local expected_nodes cpu_results node result confirm
local -a unsupported=() offline=()
echo "Checking that Elastic Agent nodes support the x86-64-v3 CPU instruction set now required by Elastic."
if [[ "$SKIP_CPU_CHECK" == "true" ]]; then
printf "\nSkipping the x86-64-v3 CPU check because --skip-cpu-check was specified.\n\n"
return
fi
# Nodes that never answer are absent from the results, so diff against who should have.
expected_nodes=$(salt -C "$cpu_target" --preview-target --out=json 2>/dev/null | jq -r '.[]?') || true
if [[ -z "$expected_nodes" ]]; then
printf "\nCould not determine which nodes run the Elastic Agent, so the x86-64-v3 CPU check cannot run.\n"
no_soup_for_you
fi
cpu_results=$(salt -t 30 -C "$cpu_target" cmd.run "/lib64/ld-linux-x86-64.so.2 --help | grep x86-64-v3" --out=json 2>/dev/null) || true
while IFS= read -r node; do
[[ -z "$node" ]] && continue
result=$(jq -r --arg node "$node" '.[$node] // empty' <<< "$cpu_results" 2>/dev/null)
if [[ -z "$result" || "$result" == *"did not return"* ]]; then
offline+=("$node")
elif [[ "$result" != *"x86-64-v3 (supported"* ]]; then
# glibc appends "(supported, searched)" only when supported; the open paren keeps
# this from matching a future "(unsupported".
unsupported+=("$node")
fi
done <<< "$expected_nodes"
if [[ ${#unsupported[@]} -eq 0 && ${#offline[@]} -eq 0 ]]; then
printf "\nAll Elastic Agent nodes support x86-64-v3. We can proceed with SOUP.\n\n"
return
fi
echo ""
if [[ ${#unsupported[@]} -gt 0 ]]; then
echo "The following node(s) do NOT support the x86-64-v3 CPU instruction set:"
printf ' %s\n' "${unsupported[@]}"
echo ""
echo "Upstream Elastic now builds its binaries for x86-64-v3, so these nodes can no"
echo "longer run Elastic. Upgrading them WILL BREAK them."
echo ""
fi
if [[ ${#offline[@]} -gt 0 ]]; then
echo "The following node(s) did not respond and could not be checked:"
printf ' %s\n' "${offline[@]}"
echo ""
echo "These nodes are offline, so we cannot confirm they support x86-64-v3, which"
echo "upstream Elastic now requires."
echo ""
fi
if [[ -n $UNATTENDED ]]; then
echo "Unattended mode cannot prompt for an override. Re-run soup interactively, or pass --skip-cpu-check to bypass this check."
no_soup_for_you
fi
read -rp "Type 'override' to continue anyway, or press Enter to exit: " confirm
if [[ "${confirm,,}" == "override" ]]; then
printf "\nOverride accepted. Continuing at your own risk.\n\n"
else else
no_soup_for_you printf "\nThe Elasticsearch cluster is not green. Please resolve the cluster health issue so the cluster is green before running SOUP again.\n\n"
exit 0
fi fi
} }
@@ -531,61 +442,18 @@ get_soup_script_hashes() {
} }
highstate() { highstate() {
# Run a highstate with a retry attempt. # Run a highstate.
if salt-call state.highstate -l info queue=True; then
return 0
fi
echo "Initial highstate attempt had a problem; retrying in 30 seconds."
sleep 30
salt-call state.highstate -l info queue=True salt-call state.highstate -l info queue=True
} }
upgrade_searchnode_elasticsearch() {
# Run the elasticsearch state across the true elastic cluster (non-heavy) with a retry attempt
# Excludes the manager, so that kibana & elasticfleet are not upgraded until searchnodes are upgraded.
echo "Getting ready to upgrade Elasticsearch across the grid. This may take a while..."
if salt -b 10% -C "I@elasticsearch:enabled and not G@role:so-heavynode and not G@id:${MINIONID}" state.apply elasticsearch queue=True; then
return 0
fi
echo "Initial elasticsearch state attempt had a problem; retrying in 30 seconds."
sleep 30
salt -b 10% -C "I@elasticsearch:enabled and not G@role:so-heavynode and not G@id:${MINIONID}" state.apply elasticsearch queue=True
}
push_grid_highstate() {
# Drive a batched, role-tiered highstate across the rest of the grid so remote minions
# pick up this upgrade now instead of waiting up to ~2.5 hours for their own scheduled
# highstate (the schedule moved from 15 to 120 minutes). so-soup-grid-highstate does the work
# via orch.push_batch; it only exists once the manager highstate has deployed this
# version's sbin files, so guard on it. Launch fully detached (setsid) so it survives an
# SSH drop, and never let it affect soup's exit status -- it is best-effort with the
# scheduled highstate as backstop.
if [[ ! -x /usr/sbin/so-soup-grid-highstate ]]; then
echo "so-soup-grid-highstate not present; remote nodes will converge on their scheduled highstate."
return 0
fi
local extra_args=()
if [[ $SALTUPGRADED == true || $UPGRADESALT -eq 1 ]]; then
extra_args+=(--salt-upgraded)
fi
if [[ -n "$BATCHSIZE" ]]; then
extra_args+=(--batch "$BATCHSIZE")
fi
echo "Dispatching a grid-wide highstate to remote nodes. Progress: /opt/so/log/salt/so-soup-grid-highstate"
setsid nohup /usr/sbin/so-soup-grid-highstate --reason soup "${extra_args[@]}" >/dev/null 2>&1 &
}
masterlock() { masterlock() {
echo "Locking Salt Master" echo "Locking Salt Master"
mv -v $TOPFILE $BACKUPTOPFILE mv -v $TOPFILE $BACKUPTOPFILE
echo "base:" > $TOPFILE # Render the real top file only for the host running soup; every other
echo " $MINIONID:" >> $TOPFILE # minion gets an empty top (no states) while the master is upgrading.
echo " - ca" >> $TOPFILE echo "{% if grains['id'] == '$MINIONID' %}" > $TOPFILE
echo " - elasticsearch" >> $TOPFILE cat $BACKUPTOPFILE >> $TOPFILE
echo "{% endif %}" >> $TOPFILE
} }
masterunlock() { masterunlock() {
@@ -605,8 +473,6 @@ preupgrade_changes() {
[[ "$INSTALLEDVERSION" =~ ^2\.4\.21[0-9]+$ ]] && up_to_3.0.0 [[ "$INSTALLEDVERSION" =~ ^2\.4\.21[0-9]+$ ]] && up_to_3.0.0
[[ "$INSTALLEDVERSION" == "3.0.0" ]] && up_to_3.1.0 [[ "$INSTALLEDVERSION" == "3.0.0" ]] && up_to_3.1.0
[[ "$INSTALLEDVERSION" == "3.1.0" ]] && up_to_3.2.0 [[ "$INSTALLEDVERSION" == "3.1.0" ]] && up_to_3.2.0
[[ "$INSTALLEDVERSION" == "3.2.0" ]] && up_to_3.3.0
[[ "$INSTALLEDVERSION" == "3.3.0" ]] && up_to_3.4.0
true true
} }
@@ -624,8 +490,6 @@ postupgrade_changes() {
[[ "$POSTVERSION" =~ ^2\.4\.21[0-9]+$ ]] && post_to_3.0.0 [[ "$POSTVERSION" =~ ^2\.4\.21[0-9]+$ ]] && post_to_3.0.0
[[ "$POSTVERSION" == "3.0.0" ]] && post_to_3.1.0 [[ "$POSTVERSION" == "3.0.0" ]] && post_to_3.1.0
[[ "$POSTVERSION" == "3.1.0" ]] && post_to_3.2.0 [[ "$POSTVERSION" == "3.1.0" ]] && post_to_3.2.0
[[ "$POSTVERSION" == "3.2.0" ]] && post_to_3.3.0
[[ "$POSTVERSION" == "3.3.0" ]] && post_to_3.4.0
# All applicable post-upgrade steps completed; clear the resume marker. # All applicable post-upgrade steps completed; clear the resume marker.
rm -f "$POSTVERSION_FILE" rm -f "$POSTVERSION_FILE"
true true
@@ -1008,7 +872,7 @@ post_to_3.1.0() {
recollate_postgres() { recollate_postgres() {
echo "" echo ""
echo "Recollating PostgreSQL databases. The following output may contain warnings about a version mismatch, followed by a note indicating that the collation version has been changed." echo "Recollating PostgreSQL databases. The following output may contain warnings about a version mismatch, followed by a note indicating that the collation version has been changed."
for db in template1 postgres securityonion so_telegraf; do for db in postgres securityonion so_telegraf; do
docker exec so-postgres psql -U postgres $db -c "reindex database $db" docker exec so-postgres psql -U postgres $db -c "reindex database $db"
docker exec so-postgres psql -U postgres $db -c "alter database $db refresh collation version" docker exec so-postgres psql -U postgres $db -c "alter database $db refresh collation version"
done done
@@ -1103,14 +967,13 @@ update_kafka_metadata() {
up_to_3.2.0() { up_to_3.2.0() {
ensure_salt_local_pillar ensure_salt_local_pillar
# download 9.3.7 elastic agent packages
determine_elastic_agent_upgrade
fix_logstash_0013_lumberjack_pipeline_name fix_logstash_0013_lumberjack_pipeline_name
pin_elasticsearch_data_retention_method pin_elasticsearch_data_retention_method
# Run so-elastic-fleet-es-url update with --force to ensure eval/import have
# configured so-manager_elasicsearch as the default output for both monitoring and logs
/usr/sbin/so-elastic-fleet-es-url-update --force
INSTALLEDVERSION=3.2.0 INSTALLEDVERSION=3.2.0
} }
@@ -1121,137 +984,19 @@ post_to_3.2.0() {
# SOC database bootstrap is handled by the postgres.enabled highstate. # SOC database bootstrap is handled by the postgres.enabled highstate.
scrub_postgres_log_passwords scrub_postgres_log_passwords
# Generate 9.3.7 elastic agent installers
echo "Regenerating Elastic Agent Installers"
/sbin/so-elastic-agent-gen-installers
kibana_backport_streams_index_template kibana_backport_streams_index_template
update_kafka_metadata "4.3" update_kafka_metadata "4.3"
set_postversion 3.2.0 set_postversion 3.2.0
} }
### 3.2.0 End ### ### 3.2.0 End ###
### 3.3.0 Scripts ###
up_to_3.3.0() {
# download 9.4.5 elastic agent packages
determine_elastic_agent_upgrade
# remove existing (patched) elasticsearch index template to match integration naming change
if ! remove_elasticsearch_index_template "so-logs-sentinel_one_cloud_funnel.login" "sentinel_one_cloud_funnel.login changed to sentinel_one_cloud_funnel.logins"; then
FINAL_MESSAGE_QUEUE+=("WARNING: Unable to automatically remove the so-logs-sentinel_one_cloud_funnel.login index template. This step can be performed manually using the following command:")
FINAL_MESSAGE_QUEUE+=(" - sudo so-elasticsearch-query _index_template/so-logs-sentinel_one_cloud_funnel.login -XDELETE && so-checkin")
fi
INSTALLEDVERSION=3.3.0
}
telegraf_repair() {
# Only grids whose Telegraf partitions stalled need this; --check exits 1
# when there is something to repair, so everyone else is left alone.
local repair=/usr/sbin/so-telegraf-repair
[[ -x "$repair" ]] || return 0
docker ps --format '{{.Names}}' | grep -qx so-postgres || return 0
echo "Checking Telegraf metric partitions."
local status=0
"$repair" --check >> "$SOUP_LOG" 2>&1 || status=$?
case "$status" in
0) echo " Telegraf partitions are healthy; nothing to repair." ;;
1) echo " Repairing stalled Telegraf partitions."
"$repair" --yes \
|| echo " warning: so-telegraf-repair failed; run it manually" >&2 ;;
*) echo " Skipping; Telegraf is not storing metrics in Postgres on this host." ;;
esac
}
post_to_3.3.0() {
# Recollate again since some internal DBs were excluded during 3.2.0 soup
recollate_postgres
# Generate 9.4.5 elastic agent installers
echo "Regenerating Elastic Agent Installers"
/sbin/so-elastic-agent-gen-installers
telegraf_repair
set_postversion 3.3.0
}
### 3.3.0 End ###
### 3.4.0 Scripts ###
up_to_3.4.0() {
set_soauth_range
echo "Removing so-kratos, so-hydra and so-soc so they are recreated on the soauth network."
docker rm -f so-kratos so-hydra so-soc >> $SOUP_LOG 2>&1
INSTALLEDVERSION=3.4.0
}
set_soauth_range() {
local pillar_file=/opt/so/saltstack/local/pillar/docker/soc_docker.sls
local current_range suggested authnet authgw input
[[ -f "$pillar_file" ]] || return 0
current_range=$(so-yaml.py get -r "$pillar_file" docker.range 2>/dev/null) || return 0
# A default range gets the 172.17.2.0/24 from docker/defaults.yaml, same as a fresh
# install, so there is nothing to ask about.
[[ -n "$current_range" && "$current_range" != "$DEFAULT_DOCKER_RANGE" ]] || return 0
if so-yaml.py get -r "$pillar_file" docker.networks.soauth.range >/dev/null 2>&1; then
return 0
fi
suggested=$(echo "${current_range%%/*}" | awk -F'.' '{ printf "%s.%s.%s.%s", $1, $2, ($3 + 1) % 256, $4 }')
if [[ -z $UNATTENDED ]]; then
echo ""
echo "This grid uses a custom Docker range ($current_range). The authentication"
echo "services are moving to their own isolated network, which needs a second /24"
echo "that does not overlap it."
echo ""
while :; do
read -rp "Enter the network without the /24 suffix, or press Enter for ${suggested}: " input
[[ -z "$input" ]] && input="$suggested"
if valid_soauth_range "$input" "$current_range"; then
authnet="$input"
break
fi
echo "That range must be a valid IPv4 network, must not be within 172.17.0.0/24, and must not overlap ${current_range}."
done
else
if ! valid_soauth_range "$suggested" "$current_range"; then
FINAL_MESSAGE_QUEUE+=("WARNING: Unable to pick a range for the authentication network alongside $current_range. Set it manually before the next highstate:")
FINAL_MESSAGE_QUEUE+=(" - so-yaml.py add $pillar_file docker.networks.soauth.range <network>/24")
FINAL_MESSAGE_QUEUE+=(" - so-yaml.py add $pillar_file docker.networks.soauth.gateway <gateway>")
return 0
fi
authnet="$suggested"
FINAL_MESSAGE_QUEUE+=("NOTE: The authentication services moved to an isolated Docker network and were assigned ${authnet}/24.")
FINAL_MESSAGE_QUEUE+=(" - If that conflicts with your environment, update docker.networks.soauth in $pillar_file and run so-checkin.")
fi
authgw=$(echo "$authnet" | awk -F'.' '{print $1,$2,$3,1}' OFS='.')
echo "Assigning the authentication network the range ${authnet}/24."
so-yaml.py add "$pillar_file" docker.networks.soauth.range "${authnet}/24" >> $SOUP_LOG 2>&1
so-yaml.py add "$pillar_file" docker.networks.soauth.gateway "$authgw" >> $SOUP_LOG 2>&1
}
valid_soauth_range() {
local candidate=$1 docker_range=$2
valid_ip4 "$candidate" || return 1
[[ $candidate =~ ^172\.17\.0\. ]] && return 1
[[ "${candidate}/24" == "$docker_range" ]] && return 1
return 0
}
post_to_3.4.0() {
set_postversion 3.4.0
}
### 3.4.0 End ###
repo_sync() { repo_sync() {
echo "Sync the local repo." echo "Sync the local repo."
@@ -1521,12 +1266,11 @@ verify_es_version_compatibility() {
local is_active_intermediate_upgrade=1 local is_active_intermediate_upgrade=1
# supported upgrade paths for SO-ES versions # supported upgrade paths for SO-ES versions
declare -A es_upgrade_map=( declare -A es_upgrade_map=(
["8.18.4"]="8.18.6 8.18.8 9.0.8" ["8.18.4"]="8.18.6 8.18.8 9.0.8"
["8.18.6"]="8.18.8 9.0.8" ["8.18.6"]="8.18.8 9.0.8"
["8.18.8"]="9.0.8" ["8.18.8"]="9.0.8"
["9.0.8"]="9.3.3 9.3.7 9.4.5" ["9.0.8"]="9.3.3 9.3.7"
["9.3.3"]="9.3.7 9.4.5" ["9.3.3"]="9.3.7"
["9.3.7"]="9.4.5"
) )
# Elasticsearch MUST upgrade through these versions # Elasticsearch MUST upgrade through these versions
@@ -2136,9 +1880,6 @@ main() {
echo "Let's see if we need to update Security Onion." echo "Let's see if we need to update Security Onion."
upgrade_check upgrade_check
check_cpu_compatibility
upgrade_space upgrade_space
echo "Verifying Elasticsearch version compatibility across the grid before upgrading." echo "Verifying Elasticsearch version compatibility across the grid before upgrading."
@@ -2185,9 +1926,6 @@ main() {
# rather than reporting "already latest". The soversion/pillar writes in # rather than reporting "already latest". The soversion/pillar writes in
# update_version are no-ops here since the version is unchanged for a hotfix. # update_version are no-ops here since the version is unchanged for a hotfix.
update_version update_version
# Push the hotfix out to the rest of the grid rather than waiting for the scheduled
# highstate. Hotfixes never upgrade Salt, so no --salt-upgraded pass is needed.
push_grid_highstate
else else
SOUP_UPGRADE_STARTED=true SOUP_UPGRADE_STARTED=true
echo "" echo ""
@@ -2305,28 +2043,12 @@ main() {
# ensure the mine is updated and populated before highstates run, following the salt-master restart # ensure the mine is updated and populated before highstates run, following the salt-master restart
update_salt_mine update_salt_mine
# kick off a searchnode elasticsearch upgrade
set +e
if [[ "$es_version" != "$target_es_version" ]]; then
if salt-key -L accepted | grep -q "_searchnode$" 2>/dev/null; then
# only run if there is atleast 1 searchnode
upgrade_searchnode_elasticsearch
fi
fi
set -e
highstate highstate
check_saltmaster_status check_saltmaster_status
postupgrade_changes postupgrade_changes
[[ $is_airgap -eq 0 ]] && unmount_update [[ $is_airgap -eq 0 ]] && unmount_update
if [[ "$es_version" != "$target_es_version" ]]; then
# Run final elasticsearch / fleet state on manager to ensure addon index templates are created/regenerated and loaded
echo "Running final Elastic states at $(date +"%T.%6N"), after upgrade to $NEWVERSION"
salt-call state.apply elasticsearch,elasticfleet queue=True
fi
echo "" echo ""
echo "Upgrade to $NEWVERSION complete." echo "Upgrade to $NEWVERSION complete."
@@ -2370,18 +2092,13 @@ main() {
if [[ $NUM_MINIONS -gt 1 ]]; then if [[ $NUM_MINIONS -gt 1 ]]; then
# Actively drive the rest of the grid to this version now. The scheduled highstate
# runs only every 120 minutes (salt:schedule:highstate_interval_minutes), so without
# this remote nodes could sit on the old version for a couple of hours after soup finishes.
push_grid_highstate
cat << EOF cat << EOF
This appears to be a distributed deployment. soup has dispatched a batched, grid-wide highstate to update the other nodes now: Elasticsearch data nodes (search/heavynodes) first, then receivers, then sensors and the remaining nodes. Progress is logged to /opt/so/log/salt/so-soup-grid-highstate, and you can watch nodes update from the Grid section of SOC. Do not manually restart anything until you know that all the search/heavynodes in your deployment are updated. This is especially important if you are using true clustering for Elasticsearch. This appears to be a distributed deployment. Other nodes should update themselves at the next Salt highstate (typically within 15 minutes). Do not manually restart anything until you know that all the search/heavy nodes in your deployment are updated. This is especially important if you are using true clustering for Elasticsearch.
Nodes are updated in batches, and things like network bandwidth can be a factor in how long the actual upgrade takes. If you have a heavy node on a slow link, it is going to take a while to get the containers to it. Depending on what changes happened between the versions, Elasticsearch might not be able to talk to said heavy node until the update is complete. Any node that is offline or missed a batch will converge on its own scheduled highstate (every 120 minutes by default). Each minion is on a random 15 minute check-in period and things like network bandwidth can be a factor in how long the actual upgrade takes. If you have a heavy node on a slow link, it is going to take a while to get the containers to it. Depending on what changes happened between the versions, Elasticsearch might not be able to talk to said heavy node until the update is complete.
If it looks like you’re missing data after the upgrade, please avoid restarting services and instead make sure at least one search node has completed its upgrade. The best way to do this is to run 'sudo salt-call state.highstate' from a search node and make sure there are no errors. Typically if it works on one node it will work on the rest. Sensor nodes are less complex and will update as they check in so you can monitor those from the Grid section of SOC. If it looks like you’re missing data after the upgrade, please avoid restarting services and instead make sure at least one search node has completed its upgrade. The best way to do this is to run 'sudo salt-call state.highstate' from a search node and make sure there are no errors. Typically if it works on one node it will work on the rest. Sensor nodes are less complex and will update as they check in so you can monitor those from the Grid section of SOC.
@@ -2417,25 +2134,12 @@ fi
echo "### soup has been served at $(date) ###" echo "### soup has been served at $(date) ###"
} }
SKIP_CPU_CHECK=false
declare -a SOUP_ARGS=()
for arg in "$@"; do
if [[ "$arg" == "--skip-cpu-check" ]]; then
SKIP_CPU_CHECK=true
else
SOUP_ARGS+=("$arg")
fi
done
set -- "${SOUP_ARGS[@]}"
while getopts ":b:f:y" opt; do while getopts ":b:f:y" opt; do
case ${opt} in case ${opt} in
b ) b )
BATCHSIZE="$OPTARG" BATCHSIZE="$OPTARG"
# Accept either a plain count (e.g. 5) or a percentage (e.g. 25%); passed through if ! [[ "$BATCHSIZE" =~ ^[1-9][0-9]*$ ]]; then
# to so-soup-grid-highstate --batch, which salt's batch/batch_wait accepts in both forms. echo "Batch size must be a number greater than 0."
if ! [[ "$BATCHSIZE" =~ ^[1-9][0-9]*%?$ ]]; then
echo "Batch size must be a number greater than 0, optionally with a trailing % (e.g. 5 or 25%)."
exit 1 exit 1
fi fi
;; ;;
@@ -2451,7 +2155,7 @@ while getopts ":b:f:y" opt; do
ISOLOC="$OPTARG" ISOLOC="$OPTARG"
;; ;;
\? ) \? )
echo "Usage: soup [-b] [-y] [-f <iso location>] [--skip-cpu-check]" echo "Usage: soup [-b] [-y] [-f <iso location>]"
exit 1 exit 1
;; ;;
: ) : )
+2 -2
View File
@@ -57,8 +57,8 @@ nginx_sbin:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://nginx/tools/sbin - source: salt://nginx/tools/sbin
- user: root - user: 939
- group: root - group: 939
- file_mode: 755 - file_mode: 755
#nginx_sbin_jinja: #nginx_sbin_jinja:
+9 -25
View File
@@ -96,14 +96,14 @@ http {
add_header X-XSS-Protection "1; mode=block"; add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options nosniff; add_header X-Content-Type-Options nosniff;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains"; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
add_header Referrer-Policy no-referrer; add_header referrer-Policy no-referrer;
ssl_certificate "/etc/pki/nginx/server.crt"; ssl_certificate "/etc/pki/nginx/server.crt";
ssl_certificate_key "/etc/pki/nginx/server.key"; ssl_certificate_key "/etc/pki/nginx/server.key";
ssl_session_cache shared:SSL:1m; ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m; ssl_session_timeout 10m;
ssl_ciphers TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256:TLS_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_256_CCM:TLS_RSA_WITH_ARIA_256_GCM_SHA384:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_128_CCM:TLS_RSA_WITH_ARIA_128_GCM_SHA256; ssl_ciphers TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256:TLS_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_256_CCM:TLS_RSA_WITH_ARIA_256_GCM_SHA384:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_128_CCM:TLS_RSA_WITH_ARIA_128_GCM_SHA256;
ssl_ecdh_curve X25519:secp521r1:secp384r1; ssl_ecdh_curve secp521r1:secp384r1;
ssl_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
ssl_protocols TLSv1.2 TLSv1.3; ssl_protocols TLSv1.2 TLSv1.3;
} }
@@ -138,14 +138,14 @@ http {
add_header X-XSS-Protection "1; mode=block"; add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options nosniff; add_header X-Content-Type-Options nosniff;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains"; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
add_header Referrer-Policy no-referrer; add_header referrer-Policy no-referrer;
ssl_certificate "/etc/pki/nginx/server.crt"; ssl_certificate "/etc/pki/nginx/server.crt";
ssl_certificate_key "/etc/pki/nginx/server.key"; ssl_certificate_key "/etc/pki/nginx/server.key";
ssl_session_cache shared:SSL:1m; ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m; ssl_session_timeout 10m;
ssl_ciphers TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256:TLS_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_256_CCM:TLS_RSA_WITH_ARIA_256_GCM_SHA384:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_128_CCM:TLS_RSA_WITH_ARIA_128_GCM_SHA256; ssl_ciphers TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256:TLS_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_256_CCM:TLS_RSA_WITH_ARIA_256_GCM_SHA384:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_128_CCM:TLS_RSA_WITH_ARIA_128_GCM_SHA256;
ssl_ecdh_curve X25519:secp521r1:secp384r1; ssl_ecdh_curve secp521r1:secp384r1;
ssl_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
ssl_protocols TLSv1.2 TLSv1.3; ssl_protocols TLSv1.2 TLSv1.3;
location / { location / {
@@ -172,18 +172,18 @@ http {
add_header X-XSS-Protection "1; mode=block"; add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options nosniff; add_header X-Content-Type-Options nosniff;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains"; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
add_header Referrer-Policy no-referrer; add_header referrer-Policy no-referrer;
ssl_certificate "/etc/pki/nginx/server.crt"; ssl_certificate "/etc/pki/nginx/server.crt";
ssl_certificate_key "/etc/pki/nginx/server.key"; ssl_certificate_key "/etc/pki/nginx/server.key";
ssl_session_cache shared:SSL:1m; ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m; ssl_session_timeout 10m;
ssl_ciphers TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256:TLS_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_256_CCM:TLS_RSA_WITH_ARIA_256_GCM_SHA384:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_128_CCM:TLS_RSA_WITH_ARIA_128_GCM_SHA256; ssl_ciphers TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256:TLS_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_256_CCM:TLS_RSA_WITH_ARIA_256_GCM_SHA384:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_128_CCM:TLS_RSA_WITH_ARIA_128_GCM_SHA256;
ssl_ecdh_curve X25519:secp521r1:secp384r1; ssl_ecdh_curve secp521r1:secp384r1;
ssl_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
ssl_protocols TLSv1.2 TLSv1.3; ssl_protocols TLSv1.2 TLSv1.3;
location ~* (^/login|^/login/.*|^/js/.*|^/css/.*|^/images/.*|^/pages/.*|^/docs/.*) { location ~* (^/login/.*|^/js/.*|^/css/.*|^/images/.*|^/pages/.*|^/docs/.*) {
proxy_pass http://{{ GLOBALS.manager }}:9822; proxy_pass http://{{ GLOBALS.manager }}:9822;
proxy_read_timeout 90; proxy_read_timeout 90;
proxy_connect_timeout 90; proxy_connect_timeout 90;
@@ -198,10 +198,6 @@ http {
} }
location / { location / {
if ($http_authorization ~* "^Bearer .*$") {
return 401;
}
auth_request /auth/sessions/whoami; auth_request /auth/sessions/whoami;
auth_request_set $userid $upstream_http_x_kratos_authenticated_identity_id; auth_request_set $userid $upstream_http_x_kratos_authenticated_identity_id;
proxy_set_header x-user-id $userid; proxy_set_header x-user-id $userid;
@@ -222,13 +218,6 @@ http {
add_header Cache-Control "no-cache, no-store, must-revalidate"; add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache"; add_header Pragma "no-cache";
add_header Expires "0"; add_header Expires "0";
add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval' https: data: blob: wss:; frame-ancestors 'self'";
add_header X-Frame-Options SAMEORIGIN;
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options nosniff;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
add_header Referrer-Policy no-referrer;
} }
location ~ ^/auth/.*?(login|oidc/callback) { location ~ ^/auth/.*?(login|oidc/callback) {
@@ -260,7 +249,7 @@ http {
} }
{% if 'api' in salt['pillar.get']('features', []) %} {% if 'api' in salt['pillar.get']('features', []) %}
location ~* (^/oauth2/token.*|^/\.well-known/jwks.json|^/\.well-known/openid-configuration) { location ~* (^/oauth2/token.*|^.well-known/jwks.json|^.well-known/openid-configuration) {
limit_req zone=auth_throttle burst={{ NGINXMERGED.config.throttle_login_burst }} nodelay; limit_req zone=auth_throttle burst={{ NGINXMERGED.config.throttle_login_burst }} nodelay;
limit_req_status 429; limit_req_status 429;
proxy_pass http://{{ GLOBALS.manager }}:4444; proxy_pass http://{{ GLOBALS.manager }}:4444;
@@ -394,11 +383,6 @@ http {
if ($http_authorization = "") { if ($http_authorization = "") {
return 403; return 403;
} }
if ($http_authorization ~* "^Bearer .*$") {
return 401;
}
proxy_pass http://{{ GLOBALS.manager }}:9822/; proxy_pass http://{{ GLOBALS.manager }}:9822/;
proxy_read_timeout 90; proxy_read_timeout 90;
proxy_connect_timeout 90; proxy_connect_timeout 90;
@@ -415,7 +399,7 @@ http {
error_page 429 = @error429; error_page 429 = @error429;
location @error401 { location @error401 {
if ($request_uri ~* (^.*/api/.*|^.*/login.*|^.*/logout.*|^/connect/.*|^/oauth2/.*|^/.*\.map$)) { if ($request_uri ~* (^.*/api/.*|^/connect/.*|^/oauth2/.*|^/.*\.map$)) {
return 401; return 401;
} }
+4 -4
View File
@@ -3,8 +3,6 @@
{% set BATCH = AUTOAPPLY.batch %} {% set BATCH = AUTOAPPLY.batch %}
{% set BATCH_WAIT = AUTOAPPLY.batch_wait %} {% set BATCH_WAIT = AUTOAPPLY.batch_wait %}
{# queue must be a top-level salt.state arg (kwarg is ignored); an int is max_queue and still fails on conflict #}
{% for action in actions %} {% for action in actions %}
{% if action.get('highstate') %} {% if action.get('highstate') %}
apply_highstate_{{ loop.index }}: apply_highstate_{{ loop.index }}:
@@ -14,7 +12,8 @@ apply_highstate_{{ loop.index }}:
- highstate: True - highstate: True
- batch: {{ action.get('batch', BATCH) }} - batch: {{ action.get('batch', BATCH) }}
- batch_wait: {{ action.get('batch_wait', BATCH_WAIT) }} - batch_wait: {{ action.get('batch_wait', BATCH_WAIT) }}
- queue: True - kwarg:
queue: 2
{% else %} {% else %}
refresh_pillar_{{ loop.index }}: refresh_pillar_{{ loop.index }}:
salt.function: salt.function:
@@ -30,7 +29,8 @@ apply_{{ action.state | replace('.', '_') }}_{{ loop.index }}:
- {{ action.state }} - {{ action.state }}
- batch: {{ action.get('batch', BATCH) }} - batch: {{ action.get('batch', BATCH) }}
- batch_wait: {{ action.get('batch_wait', BATCH_WAIT) }} - batch_wait: {{ action.get('batch_wait', BATCH_WAIT) }}
- queue: True - kwarg:
queue: 2
- require: - require:
- salt: refresh_pillar_{{ loop.index }} - salt: refresh_pillar_{{ loop.index }}
{% endif %} {% endif %}
+1 -1
View File
@@ -16,4 +16,4 @@ postgres:
logging_collector: 'off' logging_collector: 'off'
log_min_messages: 'warning' log_min_messages: 'warning'
shared_preload_libraries: pg_cron shared_preload_libraries: pg_cron
cron.database_name: postgres cron.database_name: so_telegraf
-2
View File
@@ -29,8 +29,6 @@ psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-E
-- revoking CONNECT closes the soft edge entirely. -- revoking CONNECT closes the soft edge entirely.
REVOKE CONNECT ON DATABASE "$POSTGRES_DB" FROM PUBLIC; REVOKE CONNECT ON DATABASE "$POSTGRES_DB" FROM PUBLIC;
GRANT CONNECT ON DATABASE "$POSTGRES_DB" TO "$SO_POSTGRES_USER"; GRANT CONNECT ON DATABASE "$POSTGRES_DB" TO "$SO_POSTGRES_USER";
CREATE EXTENSION IF NOT EXISTS vector;
EOSQL EOSQL
# Bootstrap the Telegraf metrics database. Per-minion roles + schemas are # Bootstrap the Telegraf metrics database. Per-minion roles + schemas are
+1 -1
View File
@@ -83,7 +83,7 @@ postgres:
advanced: True advanced: True
helpLink: postgres helpLink: postgres
cron.database_name: cron.database_name:
description: Database pg_cron keeps its job metadata in. Must already exist when PostgreSQL starts, because pg_cron's launcher connects to it at startup and never retries if it is missing. The maintenance job itself targets so_telegraf. description: Database pg_cron schedules jobs in. Must be so_telegraf so partman maintenance runs in the right database context.
global: True global: True
advanced: True advanced: True
helpLink: postgres helpLink: postgres
+1 -6
View File
@@ -47,12 +47,7 @@ trap 'rm -f "$TMPFILE"' EXIT
# Dump all databases and roles, compress. Write to a temp file so the final # Dump all databases and roles, compress. Write to a temp file so the final
# filename only ever appears for a complete, verified backup. # filename only ever appears for a complete, verified backup.
# if ! docker exec so-postgres pg_dumpall -U postgres | gzip > "$TMPFILE"; then
# so_telegraf is excluded: it is transient metrics on a short retention window,
# it dominates the dump size, and it is rebuilt automatically after a restore --
# init-db.sh recreates the database and Telegraf recreates its tables on first
# write. Roles are globals, so the per-minion telegraf logins are still dumped.
if ! docker exec so-postgres pg_dumpall -U postgres --exclude-database=so_telegraf | gzip > "$TMPFILE"; then
log "ERROR: pg_dumpall/gzip failed; backup aborted" log "ERROR: pg_dumpall/gzip failed; backup aborted"
exit 1 exit 1
fi fi
+7 -190
View File
@@ -5,18 +5,11 @@ set -e
# Usage: so-telegraf-postgres <subcommand> # Usage: so-telegraf-postgres <subcommand>
# create_db Ensure the so_telegraf database exists. # create_db Ensure the so_telegraf database exists.
# group_role Provision the so_telegraf group role, telegraf/partman schemas, # group_role Provision the so_telegraf group role, telegraf/partman schemas,
# pg_partman, the so_admin maintenance routines, and the hourly # pg_partman, pg_cron, and the hourly partman maintenance job.
# pg_cron maintenance job.
# user Create or update a per-minion login role granted to so_telegraf. # user Create or update a per-minion login role granted to so_telegraf.
# Env: ROLE_USER, ROLE_PASS. # Env: ROLE_USER, ROLE_PASS.
# retention Reconcile partman retention and premake on telegraf parents. # retention Reconcile partman retention on telegraf parents.
# Env: RETENTION_DAYS. # Env: RETENTION_DAYS.
# maintenance Drain default partitions and run partman maintenance.
# check Report partition health. Non-zero if any parent is unhealthy.
#
# A default partition holding rows for a day blocks creating that day's child,
# so maintenance drains defaults before calling partman. Use so-telegraf-repair
# on a grid already stuck in that state.
cmd="${1:?subcommand required}" cmd="${1:?subcommand required}"
@@ -43,6 +36,7 @@ CREATE SCHEMA IF NOT EXISTS telegraf AUTHORIZATION so_telegraf;
GRANT USAGE, CREATE ON SCHEMA telegraf TO so_telegraf; GRANT USAGE, CREATE ON SCHEMA telegraf TO so_telegraf;
CREATE SCHEMA IF NOT EXISTS partman; CREATE SCHEMA IF NOT EXISTS partman;
CREATE EXTENSION IF NOT EXISTS pg_partman SCHEMA partman; CREATE EXTENSION IF NOT EXISTS pg_partman SCHEMA partman;
CREATE EXTENSION IF NOT EXISTS pg_cron;
-- Telegraf (running as so_telegraf) calls partman.create_parent() -- Telegraf (running as so_telegraf) calls partman.create_parent()
-- on first write of each metric, which needs USAGE on the partman -- on first write of each metric, which needs USAGE on the partman
-- schema, EXECUTE on its functions/procedures, and write access to -- schema, EXECUTE on its functions/procedures, and write access to
@@ -57,141 +51,12 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA partman
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO so_telegraf; GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO so_telegraf;
ALTER DEFAULT PRIVILEGES IN SCHEMA partman ALTER DEFAULT PRIVILEGES IN SCHEMA partman
GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO so_telegraf; GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO so_telegraf;
-- Hourly partman maintenance. cron.schedule is idempotent by jobname.
-- pg_cron runs these as postgres, so they must not sit in a schema any SELECT cron.schedule(
-- Telegraf role can create objects in.
CREATE SCHEMA IF NOT EXISTS so_admin AUTHORIZATION postgres;
REVOKE ALL ON SCHEMA so_admin FROM PUBLIC;
CREATE OR REPLACE PROCEDURE so_admin.telegraf_maintenance()
LANGUAGE plpgsql
AS $proc$
DECLARE
r record;
v_default text;
v_rows bigint;
BEGIN
-- No per-parent EXCEPTION handler: partition_data_proc commits internally,
-- and COMMIT is illegal while a subtransaction is active. A failing parent
-- aborts the run and the next pass retries.
FOR r IN
SELECT parent_table, retention
FROM partman.part_config
WHERE parent_table LIKE 'telegraf.%'
ORDER BY parent_table
LOOP
v_default := format('%I.%I',
split_part(r.parent_table, '.', 1),
split_part(r.parent_table, '.', 2) || '_default');
CONTINUE WHEN to_regclass(v_default) IS NULL;
EXECUTE format('SELECT count(*) FROM %s', v_default) INTO v_rows;
CONTINUE WHEN v_rows = 0;
RAISE WARNING 'so_admin.telegraf_maintenance: % rows stranded in %, draining',
v_rows, v_default;
-- Cheaper to delete expired rows than to repartition and then drop them.
IF r.retention IS NOT NULL THEN
EXECUTE format('DELETE FROM %s WHERE "time" < now() - %L::interval',
v_default, r.retention);
COMMIT;
END IF;
-- Bounded so a large backlog drains across several runs.
CALL partman.partition_data_proc(
p_parent_table := r.parent_table,
p_loop_count := 200,
p_source_table := v_default
);
COMMIT;
END LOOP;
CALL partman.run_maintenance_proc();
END;
$proc$;
CREATE OR REPLACE FUNCTION so_admin.telegraf_partition_status()
RETURNS TABLE (
parent_table text,
oldest_child date,
newest_child date,
days_ahead int,
retention text,
default_rows bigint,
default_size text
)
LANGUAGE plpgsql
AS $func$
DECLARE
r record;
v_default regclass;
BEGIN
FOR r IN
SELECT pc.parent_table AS pt, pc.retention AS ret
FROM partman.part_config pc
WHERE pc.parent_table LIKE 'telegraf.%'
ORDER BY pc.parent_table
LOOP
parent_table := r.pt;
retention := r.ret;
SELECT min(d), max(d) INTO oldest_child, newest_child
FROM (
SELECT to_date(substring(c.relname FROM '_p(\d{8})$'), 'YYYYMMDD') AS d
FROM pg_inherits i
JOIN pg_class c ON c.oid = i.inhrelid
WHERE i.inhparent = r.pt::regclass
AND pg_get_expr(c.relpartbound, c.oid) <> 'DEFAULT'
) s;
days_ahead := newest_child - current_date;
v_default := to_regclass(format('%I.%I',
split_part(r.pt, '.', 1),
split_part(r.pt, '.', 2) || '_default'));
IF v_default IS NULL THEN
default_rows := NULL;
default_size := NULL;
ELSE
EXECUTE format('SELECT count(*) FROM %s', v_default::text) INTO default_rows;
default_size := pg_size_pretty(pg_total_relation_size(v_default));
END IF;
RETURN NEXT;
END LOOP;
END;
$func$;
-- Drop the registration older releases left in so_telegraf.
SELECT CASE
WHEN current_setting('cron.database_name', true) IS DISTINCT FROM current_database()
AND EXISTS (SELECT 1 FROM pg_catalog.pg_extension WHERE extname = 'pg_cron')
THEN 'true' ELSE 'false'
END AS drop_stale_cron \gset
\if :drop_stale_cron
DROP EXTENSION pg_cron CASCADE;
\endif
EOSQL
# Guarded on the live GUC so applying this before the postgresql.conf change
# has restarted the container skips rather than failing.
docker exec -i so-postgres psql -v ON_ERROR_STOP=1 -U postgres -d postgres <<'EOSQL'
SELECT CASE WHEN current_setting('cron.database_name', true) = current_database()
THEN 'true' ELSE 'false' END AS cron_here \gset
\if :cron_here
CREATE EXTENSION IF NOT EXISTS pg_cron;
-- cron.schedule_in_database is idempotent by jobname.
SELECT cron.schedule_in_database(
'telegraf-partman-maintenance', 'telegraf-partman-maintenance',
'17 * * * *', '17 * * * *',
'CALL so_admin.telegraf_maintenance()', 'CALL partman.run_maintenance_proc()'
'so_telegraf'
); );
\else
\echo 'pg_cron metadata database is not `postgres` yet; skipping job registration.'
\endif
EOSQL EOSQL
;; ;;
@@ -225,8 +90,6 @@ EOSQL
: "${RETENTION_DAYS:?RETENTION_DAYS is required}" : "${RETENTION_DAYS:?RETENTION_DAYS is required}"
# \gset + \if guards against a missing pg_partman without using a DO # \gset + \if guards against a missing pg_partman without using a DO
# block (psql :var substitution doesn't reach into dollar-quoted code). # block (psql :var substitution doesn't reach into dollar-quoted code).
# premake is reconciled here because telegraf.conf only applies it to
# parents created from now on.
docker exec -i so-postgres psql \ docker exec -i so-postgres psql \
-v ON_ERROR_STOP=1 \ -v ON_ERROR_STOP=1 \
-v retention_days="$RETENTION_DAYS" \ -v retention_days="$RETENTION_DAYS" \
@@ -234,60 +97,14 @@ EOSQL
SELECT CASE WHEN EXISTS (SELECT 1 FROM pg_catalog.pg_extension WHERE extname = 'pg_partman') SELECT CASE WHEN EXISTS (SELECT 1 FROM pg_catalog.pg_extension WHERE extname = 'pg_partman')
THEN 'true' ELSE 'false' END AS has_partman \gset THEN 'true' ELSE 'false' END AS has_partman \gset
\if :has_partman \if :has_partman
-- infinite_time_partitions so a gap in metrics does not stop partman from
-- premaking forward, which is what leaves everything in the default.
UPDATE partman.part_config UPDATE partman.part_config
SET retention = :'retention_days' || ' days', SET retention = :'retention_days' || ' days',
retention_keep_table = false, retention_keep_table = false
premake = 7,
infinite_time_partitions = true
WHERE parent_table LIKE 'telegraf.%'; WHERE parent_table LIKE 'telegraf.%';
\endif \endif
EOSQL EOSQL
;; ;;
maintenance)
docker exec -i so-postgres psql -v ON_ERROR_STOP=1 -U postgres -d so_telegraf <<'EOSQL'
SELECT CASE WHEN to_regproc('so_admin.telegraf_maintenance') IS NOT NULL
THEN 'true' ELSE 'false' END AS has_proc \gset
\if :has_proc
CALL so_admin.telegraf_maintenance();
\else
\echo 'so_admin.telegraf_maintenance() is missing; run so-telegraf-postgres group_role first.'
\endif
EOSQL
;;
check)
docker exec -i so-postgres psql -U postgres -d so_telegraf <<'EOSQL'
\pset border 2
SELECT * FROM so_admin.telegraf_partition_status();
EOSQL
docker exec -i so-postgres psql -U postgres -d postgres <<'EOSQL'
\pset border 2
SELECT CASE WHEN to_regclass('cron.job_run_details') IS NOT NULL
THEN 'true' ELSE 'false' END AS has_cron \gset
\if :has_cron
SELECT d.status, d.return_message, d.start_time
FROM cron.job_run_details d
JOIN cron.job j ON j.jobid = d.jobid
WHERE j.jobname = 'telegraf-partman-maintenance'
ORDER BY d.start_time DESC
LIMIT 5;
\else
\echo 'pg_cron is not installed in this database.'
\endif
EOSQL
unhealthy=$(docker exec so-postgres psql -U postgres -d so_telegraf -tAc \
"SELECT count(*) FROM so_admin.telegraf_partition_status()
WHERE coalesce(default_rows, 0) > 0 OR coalesce(days_ahead, -1) < 1")
if [ "${unhealthy:-1}" != "0" ]; then
echo "so-telegraf-postgres check: $unhealthy telegraf parent(s) unhealthy" >&2
exit 1
fi
echo "so-telegraf-postgres check: all telegraf parents healthy"
;;
*) *)
echo "Unknown subcommand: $cmd" >&2 echo "Unknown subcommand: $cmd" >&2
exit 1 exit 1
-245
View File
@@ -1,245 +0,0 @@
#!/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.
# Put Telegraf metrics storage back in service on a grid where pg_partman
# maintenance stalled: raises premake, discards the rows stranded in default
# partitions, restarts so-postgres if pg_cron's launcher is dead, and runs
# maintenance once. Healthy grids are reported and left alone.
#
# Usage: so-telegraf-repair [--check] [--yes] [--no-restart]
# --check Report health and change nothing.
# --yes Skip the confirmation prompt (for soup and other automation).
# --no-restart Never restart so-postgres, even if pg_cron's launcher is dead.
#
# Exit status:
# 0 healthy, or repair completed
# 1 repair is needed (--check only)
# 2 cannot run here: so-postgres, so_telegraf or pg_partman is missing
set -e
# Matches p_premake in telegraf.conf's create_parent template.
PREMAKE=7
JOB_NAME=telegraf-partman-maintenance
CHECK_ONLY=false
ASSUME_YES=false
NO_RESTART=false
usage() { sed -n '/^# Usage:/,/^# 2 /p' "$0" | sed 's/^# \?//'; }
while [[ $# -gt 0 ]]; do
case "$1" in
--check|--dry-run) CHECK_ONLY=true ;;
--yes|-y) ASSUME_YES=true ;;
--no-restart) NO_RESTART=true ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;;
esac
shift
done
skip() { echo "$*"; exit 2; }
psql_tg() { docker exec -i so-postgres psql -U postgres -d so_telegraf "$@"; }
psql_pg() { docker exec -i so-postgres psql -U postgres -d postgres "$@"; }
# query_to_xml so the per-table row counts need no helper function installed.
REPORT="
WITH parents AS (
SELECT pc.parent_table,
pc.premake,
split_part(pc.parent_table, '.', 1) AS sch,
split_part(pc.parent_table, '.', 2) AS tbl
FROM partman.part_config pc
WHERE pc.parent_table LIKE 'telegraf.%'
), children AS (
SELECT p.parent_table,
max(to_date(substring(c.relname FROM '_p(\d{8})\$'), 'YYYYMMDD')) AS newest_child
FROM parents p
JOIN pg_class pt ON pt.oid = p.parent_table::regclass
JOIN pg_inherits i ON i.inhparent = pt.oid
JOIN pg_class c ON c.oid = i.inhrelid
WHERE pg_get_expr(c.relpartbound, c.oid) <> 'DEFAULT'
GROUP BY p.parent_table
), defaults AS (
SELECT p.parent_table,
p.premake,
format('%I.%I', p.sch, p.tbl || '_default') AS default_table,
to_regclass(format('%I.%I', p.sch, p.tbl || '_default')) AS default_oid
FROM parents p
)
SELECT d.parent_table,
c.newest_child,
(c.newest_child - current_date) AS days_ahead,
d.premake,
CASE WHEN d.default_oid IS NULL THEN NULL ELSE
(xpath('/row/cnt/text()',
query_to_xml(format('SELECT count(*) AS cnt FROM %s', d.default_table),
false, true, '')))[1]::text::bigint
END AS default_rows,
CASE WHEN d.default_oid IS NULL THEN NULL
ELSE pg_size_pretty(pg_total_relation_size(d.default_oid)) END AS default_size
FROM defaults d
LEFT JOIN children c ON c.parent_table = d.parent_table
ORDER BY 1
"
docker ps --format '{{.Names}}' | grep -qx so-postgres \
|| skip "so-postgres is not running; nothing to repair."
docker exec so-postgres psql -U postgres -tAc \
"SELECT 1 FROM pg_database WHERE datname='so_telegraf'" | grep -q 1 \
|| skip "The so_telegraf database does not exist; Telegraf is not writing to Postgres."
psql_tg -tAc "SELECT 1 FROM pg_extension WHERE extname='pg_partman'" | grep -q 1 \
|| skip "pg_partman is not installed in so_telegraf; nothing to repair."
parents=$(psql_tg -tAc \
"SELECT count(*) FROM partman.part_config WHERE parent_table LIKE 'telegraf.%'")
stranded=$(psql_tg -tAc "SELECT coalesce(sum(default_rows), 0) FROM ( $REPORT ) t")
behind=$(psql_tg -tAc \
"SELECT count(*) FROM ( $REPORT ) t WHERE coalesce(days_ahead, -1) < 1")
# premake < 7, or infinite_time_partitions off: without the latter partman
# refuses to premake forward across the gap the stall left behind.
misconfigured=$(psql_tg -tAc \
"SELECT count(*) FROM partman.part_config
WHERE parent_table LIKE 'telegraf.%'
AND (premake < $PREMAKE OR NOT infinite_time_partitions)")
# Both columns are matched because which one carries the launcher's name varies
# with the pg_cron version.
launcher=$(psql_pg -tAc \
"SELECT count(*) FROM pg_stat_activity
WHERE backend_type ILIKE '%pg_cron%' OR application_name ILIKE '%pg_cron%'")
# so_telegraf before the postgres state lands, postgres after.
cron_db=$(docker exec so-postgres psql -U postgres -tAc \
"SELECT current_setting('cron.database_name', true)" | tr -d '[:space:]')
last_run=never
if [[ -n "$cron_db" ]]; then
last_run=$(docker exec so-postgres psql -U postgres -d "$cron_db" -tAc \
"SELECT coalesce(max(d.start_time)::text, 'never')
FROM cron.job_run_details d JOIN cron.job j USING (jobid)
WHERE j.jobname = '$JOB_NAME'" 2>/dev/null | tr -d '[:space:]' || echo unknown)
[[ -n "$last_run" ]] || last_run=never
fi
# A grid that has never written a metric has nothing to recover, and an empty
# cron_db means pg_cron is not loaded at all, which no restart fixes.
restart_needed=false
[[ "$launcher" -eq 0 && "$parents" -gt 0 && -n "$cron_db" ]] && restart_needed=true
repair_needed=false
[[ "$stranded" -gt 0 ]] && repair_needed=true
[[ "$behind" -gt 0 ]] && repair_needed=true
[[ "$misconfigured" -gt 0 ]] && repair_needed=true
$restart_needed && repair_needed=true
echo "Telegraf partition status:"
psql_tg -c "$REPORT"
echo "Rows stranded in default partitions: $stranded"
echo "pg_cron metadata database: ${cron_db:-unset}"
echo "pg_cron launcher running: $([[ "$launcher" -gt 0 ]] && echo yes || echo no)"
echo "Last $JOB_NAME run: $last_run"
echo
if ! $repair_needed; then
echo "Telegraf partitions are healthy. Nothing to do."
exit 0
fi
if $CHECK_ONLY; then
echo "Repair is needed:"
[[ "$stranded" -gt 0 ]] && echo " * $stranded row(s) stranded in default partitions"
[[ "$behind" -gt 0 ]] && echo " * $behind parent(s) with no partition for the current window"
[[ "$misconfigured" -gt 0 ]] && echo " * $misconfigured parent(s) with stale partman settings"
$restart_needed && echo " * pg_cron's launcher is dead; maintenance is not running at all"
echo
echo "Re-run without --check to repair."
exit 1
fi
if [[ "$stranded" -gt 0 ]] && ! $ASSUME_YES; then
echo "This will permanently discard the $stranded stranded row(s) above."
$restart_needed && ! $NO_RESTART && \
echo "so-postgres will also be restarted, which briefly interrupts SOC."
[[ -t 0 ]] || { echo "Not a terminal; re-run with --yes to confirm." >&2; exit 2; }
read -r -p "Continue? [y/N] " answer
[[ "$answer" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 0; }
fi
if [[ "$misconfigured" -gt 0 ]]; then
echo "Reconciling partman settings on $misconfigured parent(s)."
# GREATEST so an operator who raised premake further keeps their value.
psql_tg -v ON_ERROR_STOP=1 -c \
"UPDATE partman.part_config
SET premake = GREATEST(premake, $PREMAKE),
infinite_time_partitions = true
WHERE parent_table LIKE 'telegraf.%'"
fi
if [[ "$stranded" -gt 0 ]]; then
echo "Clearing default partitions."
# One transaction: Telegraf is still writing, so a default emptied without
# its partition in place is refilled before maintenance can attach one.
psql_tg -v ON_ERROR_STOP=1 <<'EOSQL'
DO $$
DECLARE
r record;
BEGIN
FOR r IN
SELECT pc.parent_table,
format('%I.%I', n.nspname, c.relname) AS default_table
FROM partman.part_config pc
JOIN pg_class p ON p.oid = pc.parent_table::regclass
JOIN pg_inherits i ON i.inhparent = p.oid
JOIN pg_class c ON c.oid = i.inhrelid
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE pc.parent_table LIKE 'telegraf.%'
AND pg_get_expr(c.relpartbound, c.oid) = 'DEFAULT'
LOOP
EXECUTE format('TRUNCATE TABLE %s', r.default_table);
PERFORM partman.create_partition_time(
r.parent_table, ARRAY[date_trunc('day', now())]::timestamptz[]);
END LOOP;
END
$$;
EOSQL
fi
if $restart_needed; then
if $NO_RESTART; then
echo "WARNING: pg_cron's launcher is dead and --no-restart was given."
echo " Maintenance will not run on its own until so-postgres is restarted."
else
echo "Restarting so-postgres to revive pg_cron's launcher."
docker restart so-postgres >/dev/null
for _ in $(seq 1 60); do
docker exec so-postgres pg_isready -U postgres -q 2>/dev/null && break
sleep 2
done
docker exec so-postgres pg_isready -U postgres -q \
|| { echo "so-postgres did not come back; check 'docker logs so-postgres'." >&2; exit 1; }
fi
fi
echo "Running partition maintenance."
# so_admin.telegraf_maintenance() only exists once the postgres state has landed.
psql_tg -v ON_ERROR_STOP=1 <<'EOSQL'
SELECT CASE WHEN to_regproc('so_admin.telegraf_maintenance') IS NOT NULL
THEN 'true' ELSE 'false' END AS has_proc \gset
\if :has_proc
CALL so_admin.telegraf_maintenance();
\else
CALL partman.run_maintenance_proc();
\endif
EOSQL
echo
echo "Telegraf partition status after repair:"
psql_tg -c "$REPORT"
echo "The $JOB_NAME job runs hourly at :17. Confirm it fired with:"
echo " so-telegraf-repair --check"
+1 -4
View File
@@ -1,6 +1,3 @@
# Read by push_pillar.sls (SOC config saves) and push_files.sls (local/salt file edits);
# both key on the app name. An app missing here waits for the next scheduled highstate.
#
# One pillar directory can map to multiple (state, tgt) actions. # One pillar directory can map to multiple (state, tgt) actions.
# tgt is a raw salt compound expression. tgt_type is always "compound". # tgt is a raw salt compound expression. tgt_type is always "compound".
# Per-action `batch` / `batch_wait` override the orch defaults (25% / 15s). # Per-action `batch` / `batch_wait` override the orch defaults (25% / 15s).
@@ -245,7 +242,7 @@ versionlock:
# grain (compound supports nested grain matching via G@<key>:<subkey>:<value>). # grain (compound supports nested grain matching via G@<key>:<subkey>:<value>).
# pillar/vm/soc_vm.sls write path is referenced at salt/_runners/setup_hypervisor.py:856. # pillar/vm/soc_vm.sls write path is referenced at salt/_runners/setup_hypervisor.py:856.
vm: vm:
- state: vm.user - state: vm
tgt: 'G@salt-cloud:driver:libvirt' tgt: 'G@salt-cloud:driver:libvirt'
# zeek: sensor_roles + so-import (5 roles). # zeek: sensor_roles + so-import (5 roles).
-131
View File
@@ -1,131 +0,0 @@
#!py
# Reactor invoked by local_files_beacon when a watched directory under
# /opt/so/saltstack/local/salt/ changes. The beacon tag is an app name in
# pillar_push_map.yaml, so file changes and pillar changes route through the same
# table -- see salt/reactor/push_pillar.sls.
#
# The app comes from the event tag, not the payload: salt's beacon loop pops the
# beacon's 'tag' key off the data and appends it to the event tag instead (see
# salt/beacons/__init__.py). The reactor renderer sets both `tag` and `data` as
# module globals.
#
# Reactors never dispatch directly. The so-push-drainer schedule picks up ready
# intents, dedupes across pending files, and dispatches orch.push_batch.
import fcntl
import json
import logging
import os
import time
from salt.client import Caller
import yaml
LOG = logging.getLogger(__name__)
PENDING_DIR = '/opt/so/state/push_pending'
LOCK_FILE = os.path.join(PENDING_DIR, '.lock')
MAX_PATHS = 20
# The pillar_push_map.yaml is shipped via salt:// but the reactor runs on the
# master, which mounts the default saltstack tree at this path.
PUSH_MAP_PATH = '/opt/so/saltstack/default/salt/reactor/pillar_push_map.yaml'
_PUSH_MAP_CACHE = {'mtime': 0, 'data': None}
def _load_push_map():
try:
st = os.stat(PUSH_MAP_PATH)
except OSError:
LOG.warning('push_files: %s not found', PUSH_MAP_PATH)
return {}
if _PUSH_MAP_CACHE['mtime'] != st.st_mtime:
try:
with open(PUSH_MAP_PATH, 'r') as f:
_PUSH_MAP_CACHE['data'] = yaml.safe_load(f) or {}
except Exception:
LOG.exception('push_files: failed to load %s', PUSH_MAP_PATH)
_PUSH_MAP_CACHE['data'] = {}
_PUSH_MAP_CACHE['mtime'] = st.st_mtime
return _PUSH_MAP_CACHE['data'] or {}
def _push_enabled():
try:
caller = Caller()
return bool(caller.cmd('pillar.get', 'salt:auto_apply:enabled', True))
except Exception:
LOG.exception('push_files: pillar.get salt:auto_apply:enabled failed, assuming enabled')
return True
def _write_intent(key, actions, path):
now = time.time()
try:
os.makedirs(PENDING_DIR, exist_ok=True)
except OSError:
LOG.exception('push_files: cannot create %s', PENDING_DIR)
return
intent_path = os.path.join(PENDING_DIR, '{}.json'.format(key))
lock_fd = os.open(LOCK_FILE, os.O_CREAT | os.O_RDWR, 0o644)
try:
fcntl.flock(lock_fd, fcntl.LOCK_EX)
intent = {}
if os.path.exists(intent_path):
try:
with open(intent_path, 'r') as f:
intent = json.load(f)
except (IOError, ValueError):
intent = {}
intent.setdefault('first_touch', now)
intent['last_touch'] = now
intent['actions'] = actions
paths = intent.get('paths', [])
if path and path not in paths:
paths.append(path)
paths = paths[-MAX_PATHS:]
intent['paths'] = paths
tmp_path = intent_path + '.tmp'
with open(tmp_path, 'w') as f:
json.dump(intent, f)
os.rename(tmp_path, intent_path)
except Exception:
LOG.exception('push_files: failed to write intent %s', intent_path)
finally:
try:
fcntl.flock(lock_fd, fcntl.LOCK_UN)
finally:
os.close(lock_fd)
def run():
if not _push_enabled():
LOG.info('push_files: push disabled, skipping')
return {}
event = data.get('data', data) # noqa: F821 -- data provided by reactor
path = event.get('path', '')
app = tag.rsplit('/', 1)[-1].strip() # noqa: F821 -- tag provided by reactor
if not app:
LOG.debug('push_files: ignoring event with no app segment: tag=%s', tag) # noqa: F821
return {}
entry = _load_push_map().get(app)
if not entry:
LOG.warning(
'push_files: app "%s" is not in pillar_push_map.yaml; change will be '
'picked up at the next scheduled highstate (path=%s)',
app, path,
)
return {}
_write_intent('files_{}'.format(app), list(entry), path)
LOG.info('push_files: intent updated for %s (path=%s)', app, path)
return {}
+5 -6
View File
@@ -138,7 +138,6 @@ def run():
# top level so the reactor is robust to either shape. # top level so the reactor is robust to either shape.
event = data.get('data', data) # noqa: F821 -- data provided by reactor event = data.get('data', data) # noqa: F821 -- data provided by reactor
setting_id = event.get('setting_id', '') setting_id = event.get('setting_id', '')
audit_id = event.get('id')
node_id = (event.get('node_id') or '').strip() node_id = (event.get('node_id') or '').strip()
app = _app_from_setting(setting_id) app = _app_from_setting(setting_id)
@@ -151,8 +150,8 @@ def run():
if not entry: if not entry:
LOG.warning( LOG.warning(
'push_pillar: app "%s" is not in pillar_push_map.yaml; change will be ' 'push_pillar: app "%s" is not in pillar_push_map.yaml; change will be '
'picked up at the next scheduled highstate (setting_id=%s audit_id=%s)', 'picked up at the next scheduled highstate (setting_id=%s)',
app, setting_id, audit_id, app, setting_id,
) )
return {} return {}
@@ -166,12 +165,12 @@ def run():
'node_{}_{}'.format(node_id, app), actions, 'node_{}_{}'.format(node_id, app), actions,
'audit:{}@{}'.format(setting_id, node_id), 'audit:{}@{}'.format(setting_id, node_id),
) )
LOG.info('push_pillar: per-node intent updated for %s on %s (setting_id=%s audit_id=%s)', LOG.info('push_pillar: per-node intent updated for %s on %s (setting_id=%s)',
app, node_id, setting_id, audit_id) app, node_id, setting_id)
return {} return {}
# Branch B: grid-wide app change -> use the map entry's actions as-is. # Branch B: grid-wide app change -> use the map entry's actions as-is.
actions = list(entry) # copy to avoid mutating the cache actions = list(entry) # copy to avoid mutating the cache
_write_intent('pillar_{}'.format(app), actions, 'audit:{}'.format(setting_id)) _write_intent('pillar_{}'.format(app), actions, 'audit:{}'.format(setting_id))
LOG.info('push_pillar: app intent updated for %s (setting_id=%s audit_id=%s)', app, setting_id, audit_id) LOG.info('push_pillar: app intent updated for %s (setting_id=%s)', app, setting_id)
return {} return {}
+96
View File
@@ -0,0 +1,96 @@
#!py
# Reactor invoked by the rules_beacon poll beacon (salt/_beacons/rules_beacon.py) on rule
# file changes under /opt/so/saltstack/local/salt/strelka/rules/compiled/.
#
# Writes (or updates) a push intent at /opt/so/state/push_pending/rules_strelka.json
# and returns {}. The so-push-drainer schedule picks up ready intents, dedupes
# across pending files, and dispatches orch.push_batch. Reactors never dispatch
# directly
import fcntl
import json
import logging
import os
import time
from salt.client import Caller
LOG = logging.getLogger(__name__)
PENDING_DIR = '/opt/so/state/push_pending'
LOCK_FILE = os.path.join(PENDING_DIR, '.lock')
MAX_PATHS = 20
# Mirrors GLOBALS.sensor_roles in salt/vars/globals.map.jinja. Sensor-side
# strelka runs on exactly these four roles; so-import gets strelka.manager
# instead, which is not fired on pillar changes.
SENSOR_ROLES = ['so-eval', 'so-heavynode', 'so-sensor', 'so-standalone']
def _sensor_compound():
return ' or '.join('G@role:{}'.format(r) for r in SENSOR_ROLES)
def _push_enabled():
try:
caller = Caller()
return bool(caller.cmd('pillar.get', 'salt:auto_apply:enabled', True))
except Exception:
LOG.exception('push_strelka: pillar.get salt:auto_apply:enabled failed, assuming enabled')
return True
def _write_intent(key, actions, path):
now = time.time()
try:
os.makedirs(PENDING_DIR, exist_ok=True)
except OSError:
LOG.exception('push_strelka: cannot create %s', PENDING_DIR)
return
intent_path = os.path.join(PENDING_DIR, '{}.json'.format(key))
lock_fd = os.open(LOCK_FILE, os.O_CREAT | os.O_RDWR, 0o644)
try:
fcntl.flock(lock_fd, fcntl.LOCK_EX)
intent = {}
if os.path.exists(intent_path):
try:
with open(intent_path, 'r') as f:
intent = json.load(f)
except (IOError, ValueError):
intent = {}
intent.setdefault('first_touch', now)
intent['last_touch'] = now
intent['actions'] = actions
paths = intent.get('paths', [])
if path and path not in paths:
paths.append(path)
paths = paths[-MAX_PATHS:]
intent['paths'] = paths
tmp_path = intent_path + '.tmp'
with open(tmp_path, 'w') as f:
json.dump(intent, f)
os.rename(tmp_path, intent_path)
except Exception:
LOG.exception('push_strelka: failed to write intent %s', intent_path)
finally:
try:
fcntl.flock(lock_fd, fcntl.LOCK_UN)
finally:
os.close(lock_fd)
def run():
if not _push_enabled():
LOG.info('push_strelka: push disabled, skipping')
return {}
path = data.get('path', '') # noqa: F821 -- data provided by reactor
actions = [{'state': 'strelka', 'tgt': _sensor_compound()}]
_write_intent('rules_strelka', actions, path)
LOG.info('push_strelka: intent updated for path=%s', path)
return {}
+95
View File
@@ -0,0 +1,95 @@
#!py
# Reactor invoked by the rules_beacon poll beacon (salt/_beacons/rules_beacon.py) on rule
# file changes under /opt/so/saltstack/local/salt/suricata/rules/.
#
# Writes (or updates) a push intent at /opt/so/state/push_pending/rules_suricata.json
# and returns {}. The so-push-drainer schedule picks up ready intents, dedupes
# across pending files, and dispatches orch.push_batch. Reactors never dispatch
# directly
import fcntl
import json
import logging
import os
import time
from salt.client import Caller
LOG = logging.getLogger(__name__)
PENDING_DIR = '/opt/so/state/push_pending'
LOCK_FILE = os.path.join(PENDING_DIR, '.lock')
MAX_PATHS = 20
# Mirrors GLOBALS.sensor_roles in salt/vars/globals.map.jinja. Suricata also
# runs on so-import per salt/top.sls, so that role is appended below.
SENSOR_ROLES = ['so-eval', 'so-heavynode', 'so-sensor', 'so-standalone']
def _sensor_compound_plus_import():
return ' or '.join('G@role:{}'.format(r) for r in SENSOR_ROLES) + ' or G@role:so-import'
def _push_enabled():
try:
caller = Caller()
return bool(caller.cmd('pillar.get', 'salt:auto_apply:enabled', True))
except Exception:
LOG.exception('push_suricata: pillar.get salt:auto_apply:enabled failed, assuming enabled')
return True
def _write_intent(key, actions, path):
now = time.time()
try:
os.makedirs(PENDING_DIR, exist_ok=True)
except OSError:
LOG.exception('push_suricata: cannot create %s', PENDING_DIR)
return
intent_path = os.path.join(PENDING_DIR, '{}.json'.format(key))
lock_fd = os.open(LOCK_FILE, os.O_CREAT | os.O_RDWR, 0o644)
try:
fcntl.flock(lock_fd, fcntl.LOCK_EX)
intent = {}
if os.path.exists(intent_path):
try:
with open(intent_path, 'r') as f:
intent = json.load(f)
except (IOError, ValueError):
intent = {}
intent.setdefault('first_touch', now)
intent['last_touch'] = now
intent['actions'] = actions
paths = intent.get('paths', [])
if path and path not in paths:
paths.append(path)
paths = paths[-MAX_PATHS:]
intent['paths'] = paths
tmp_path = intent_path + '.tmp'
with open(tmp_path, 'w') as f:
json.dump(intent, f)
os.rename(tmp_path, intent_path)
except Exception:
LOG.exception('push_suricata: failed to write intent %s', intent_path)
finally:
try:
fcntl.flock(lock_fd, fcntl.LOCK_UN)
finally:
os.close(lock_fd)
def run():
if not _push_enabled():
LOG.info('push_suricata: push disabled, skipping')
return {}
path = data.get('path', '') # noqa: F821 -- data provided by reactor
actions = [{'state': 'suricata', 'tgt': _sensor_compound_plus_import()}]
_write_intent('rules_suricata', actions, path)
LOG.info('push_suricata: intent updated for path=%s', path)
return {}
+4 -4
View File
@@ -50,16 +50,16 @@ redis_sbin:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://redis/tools/sbin - source: salt://redis/tools/sbin
- user: root - user: 939
- group: root - group: 939
- file_mode: 755 - file_mode: 755
redis_sbin_jinja: redis_sbin_jinja:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://redis/tools/sbin_jinja - source: salt://redis/tools/sbin_jinja
- user: root - user: 939
- group: root - group: 939
- file_mode: 755 - file_mode: 755
- template: jinja - template: jinja
-6
View File
@@ -60,9 +60,6 @@ so_repo:
{% endif %} {% endif %}
- enabled: 1 - enabled: 1
- gpgcheck: 1 - gpgcheck: 1
{% if not GLOBALS.is_manager %}
- sslverify: 0
{% endif %}
# Only assign the kernel repo once this node's running salt matches the version this # 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 # SO release ships. During a soup the grid is mid-salt-upgrade; gating here keeps the
@@ -80,9 +77,6 @@ so_kernel_repo:
{% endif %} {% endif %}
- enabled: 1 - enabled: 1
- gpgcheck: 1 - gpgcheck: 1
{% if not GLOBALS.is_manager %}
- sslverify: 0
{% endif %}
# Supplementary kernel repo: tolerate it being empty/unreachable (e.g. before the # 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 # manager has populated /nsm/kernelrepo) so a missing repomd.xml can't make every
# dnf/pkg operation on the grid fail. # dnf/pkg operation on the grid fail.
+2 -2
View File
@@ -3,7 +3,7 @@ salt:
enabled: true enabled: true
debounce_seconds: 30 debounce_seconds: 30
drain_interval: 15 drain_interval: 15
batch: '10%' batch: '25%'
batch_wait: 15 batch_wait: 15
schedule: schedule:
highstate_interval_minutes: 120 highstate_interval_hours: 2
+4 -2
View File
@@ -1,5 +1,7 @@
reactor: reactor:
- 'salt/beacon/*/local_files_beacon/*': - 'salt/beacon/*/rules_beacon/suricata':
- salt://reactor/push_files.sls - salt://reactor/push_suricata.sls
- 'salt/beacon/*/rules_beacon/strelka':
- salt://reactor/push_strelka.sls
- 'salt/beacon/*/postgres_pillar_beacon/audit_settings': - 'salt/beacon/*/postgres_pillar_beacon/audit_settings':
- salt://reactor/push_pillar.sls - salt://reactor/push_pillar.sls
+2 -6
View File
@@ -1,15 +1,11 @@
{% from 'vars/globals.map.jinja' import GLOBALS %} {% from 'vars/globals.map.jinja' import GLOBALS %}
{% from 'salt/schedule.map.jinja' import SCHEDULEMERGED %} {% from 'salt/schedule.map.jinja' import SCHEDULEMERGED %}
{# splay a quarter of the interval, clamped to [5 min, 30 min], so short intervals
don't get jitter larger than the interval itself #}
{% set SPLAY = [[(SCHEDULEMERGED.highstate_interval_minutes * 60 // 4) | int, 300] | max, 1800] | min %}
highstate_schedule: highstate_schedule:
schedule.present: schedule.present:
- function: state.highstate - function: state.highstate
- minutes: {{ SCHEDULEMERGED.highstate_interval_minutes }} - hours: {{ SCHEDULEMERGED.highstate_interval_hours }}
- maxrunning: 1 - maxrunning: 1
{% if not GLOBALS.is_manager %} {% if not GLOBALS.is_manager %}
- splay: {{ SPLAY }} - splay: 1800
{% endif %} {% endif %}
+2 -4
View File
@@ -3,8 +3,6 @@ salt_bootstrap:
file.managed: file.managed:
- name: /usr/sbin/bootstrap-salt.sh - name: /usr/sbin/bootstrap-salt.sh
- source: salt://salt/scripts/bootstrap-salt.sh - source: salt://salt/scripts/bootstrap-salt.sh
- user: root
- group: root
- mode: 755 - mode: 755
- show_changes: False - show_changes: False
@@ -12,6 +10,6 @@ salt_sbin:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://salt/tools/sbin - source: salt://salt/tools/sbin
- user: root - user: 939
- group: root - group: 939
- file_mode: 755 - file_mode: 755
Loaded 100 of 156 files, more files were not shown because too many files have changed in this diff. Show more