mirror of
https://github.com/Security-Onion-Solutions/securityonion.git
synced 2026-10-08 23:35:37 +02:00
Compare commits
109
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d352bc0384 | ||
|
|
d1114a0dae | ||
|
|
f4b301d71c | ||
|
|
f7dbfba178 | ||
|
|
0a628bb7e7 | ||
|
|
bd6647e775 | ||
|
|
da2c19188a | ||
|
|
90b3d37be6 | ||
|
|
fcd2f67076 | ||
|
|
a9cdd17694 | ||
|
|
b32aaac290 | ||
|
|
1aee3f28dc | ||
|
|
678cb0d5b2 | ||
|
|
31c5190a1f | ||
|
|
4ce7a06abe | ||
|
|
ba95b9bbc2 | ||
|
|
43475452b3 | ||
|
|
99322cf26a | ||
|
|
117548757f | ||
|
|
22bda63847 | ||
|
|
2a4611df45 | ||
|
|
89f8bcd19f | ||
|
|
563269cbac | ||
|
|
523c39d4f2 | ||
|
|
b4557e973c | ||
|
|
0f53a7e0bc | ||
|
|
8de8ba811a | ||
|
|
d122ee7fea | ||
|
|
9732e1c639 | ||
|
|
53f9ebcd46 | ||
|
|
47d74f1ae1 | ||
|
|
855716846a | ||
|
|
8e35d70595 | ||
|
|
e4625cfcae | ||
|
|
eb803dce0e | ||
|
|
a06f08217a | ||
|
|
29d27cf255 | ||
|
|
235a60e587 | ||
|
|
a8f7c46b0d | ||
|
|
26d895ccb7 | ||
|
|
b43efc458f | ||
|
|
21222ff119 | ||
|
|
88fa7e7fb4 | ||
|
|
efe0581892 | ||
|
|
72f60fcaa9 | ||
|
|
2684a5ca95 | ||
|
|
bcee63bde5 | ||
|
|
6ce89eb323 | ||
|
|
ebab4b0d90 | ||
|
|
db60c27da2 | ||
|
|
f4defdfde0 | ||
|
|
36652e8f23 | ||
|
|
7bef194540 | ||
|
|
e2bf2837fe | ||
|
|
06704dad22 | ||
|
|
47fe0758d0 | ||
|
|
b71fd93f9d | ||
|
|
d9eff9aa9e | ||
|
|
d8884dbd99 | ||
|
|
6c0d4c15e8 | ||
|
|
2e2f62f265 | ||
|
|
b7a11a525c | ||
|
|
8eef95ea3e | ||
|
|
65e261475d | ||
|
|
bbc28c88b7 | ||
|
|
edaacf79a7 | ||
|
|
ecc643cd33 | ||
|
|
08aaf7948e | ||
|
|
bb57545d08 | ||
|
|
0f7adbbecc | ||
|
|
aeb4fe8f50 | ||
|
|
f3aa39c5a4 | ||
|
|
24077ba974 | ||
|
|
b3567405f9 | ||
|
|
1fc5bb7afa | ||
|
|
1f1d3ded41 | ||
|
|
1e86be11b2 | ||
|
|
f4518e2620 | ||
|
|
a9f7ffc3fe | ||
|
|
b018277d68 | ||
|
|
3be603e203 | ||
|
|
84cd966736 | ||
|
|
fee401a912 | ||
|
|
496b61966f | ||
|
|
52037314be | ||
|
|
9c12c10f96 | ||
|
|
9fc9be2cc9 | ||
|
|
7245843a3c | ||
|
|
a1d17417ea | ||
|
|
bee03d5bae | ||
|
|
56e3e44d04 | ||
|
|
32d1274b80 | ||
|
|
1624e8c094 | ||
|
|
3f3f091a7f | ||
|
|
cb48909578 | ||
|
|
3057775770 | ||
|
|
e4e8b90b9c | ||
|
|
223ace6ff3 | ||
|
|
66e7863336 | ||
|
|
8f253d17a6 | ||
|
|
9652a2053b | ||
|
|
191ee159ef | ||
|
|
a8bfe955a5 | ||
|
|
bd354abe83 | ||
|
|
37782fb45c | ||
|
|
cf3a4ebc27 | ||
|
|
c49008a413 | ||
|
|
8f14e96215 | ||
|
|
23a9daf7a2 |
No files matched your search
@@ -6,6 +6,10 @@ on:
|
|||||||
- "salt/sensoroni/files/analyzers/**"
|
- "salt/sensoroni/files/analyzers/**"
|
||||||
- "salt/manager/tools/sbin/**"
|
- "salt/manager/tools/sbin/**"
|
||||||
- "salt/_beacons/**"
|
- "salt/_beacons/**"
|
||||||
|
- "salt/elastalert/files/modules/**"
|
||||||
|
- "salt/telegraf/tools/sbin_jinja/**"
|
||||||
|
- "salt/telegraf/defaults.yaml"
|
||||||
|
- "salt/telegraf/soc_telegraf.yaml"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -15,7 +19,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.14"]
|
python-version: ["3.14"]
|
||||||
python-code-path: ["salt/sensoroni/files/analyzers", "salt/manager/tools/sbin", "salt/_beacons"]
|
python-code-path: ["salt/sensoroni/files/analyzers", "salt/manager/tools/sbin", "salt/_beacons", "salt/elastalert/files/modules/so"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -34,3 +38,25 @@ 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
@@ -1,17 +1,17 @@
|
|||||||
### 3.3.0-20260908 ISO image released on 2026/09/08
|
### 3.3.0-20260911 ISO image released on 2026/09/11
|
||||||
|
|
||||||
|
|
||||||
### Download and Verify
|
### Download and Verify
|
||||||
|
|
||||||
3.3.0-20260908 ISO image:
|
3.3.0-20260911 ISO image:
|
||||||
https://download.securityonion.net/file/securityonion/securityonion-3.3.0-20260908.iso
|
https://download.securityonion.net/file/securityonion/securityonion-3.3.0-20260911.iso
|
||||||
|
|
||||||
MD5: 5A2C42D0083F2D7B4DC2178C30EBC05F
|
MD5: 12B18433D3A2198A185892FF79CF638F
|
||||||
SHA1: 505220A8A3315AFEE601C13772996018425CCD29
|
SHA1: 2B3C2E1FA7A78ED1F956E7EDCC12E32593C14EEE
|
||||||
SHA256: 6EB8401296A1D051FEC558C520D2D4AB1912A72D351A2426FBF8C87FEE2BA844
|
SHA256: 0938C73B76CE30EC9E4394D312C79EA7CAC721B6818541697279A6221F7D870D
|
||||||
|
|
||||||
Signature for ISO image:
|
Signature for ISO image:
|
||||||
https://github.com/Security-Onion-Solutions/securityonion/raw/3/main/sigs/securityonion-3.3.0-20260908.iso.sig
|
https://github.com/Security-Onion-Solutions/securityonion/raw/3/main/sigs/securityonion-3.3.0-20260911.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-20260908.iso.sig
|
wget https://github.com/Security-Onion-Solutions/securityonion/raw/3/main/sigs/securityonion-3.3.0-20260911.iso.sig
|
||||||
```
|
```
|
||||||
|
|
||||||
Download the ISO image:
|
Download the ISO image:
|
||||||
```
|
```
|
||||||
wget https://download.securityonion.net/file/securityonion/securityonion-3.3.0-20260908.iso
|
wget https://download.securityonion.net/file/securityonion/securityonion-3.3.0-20260911.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-20260908.iso.sig securityonion-3.3.0-20260908.iso
|
gpg --verify securityonion-3.3.0-20260911.iso.sig securityonion-3.3.0-20260911.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 Tue 08 Sep 2026 10:07:12 AM EDT using RSA key ID FE507013
|
gpg: Signature made Fri 11 Sep 2026 11:23:56 AM 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.
|
||||||
|
|||||||
+20
-5
@@ -117,14 +117,25 @@ 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: 939
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- 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
|
||||||
@@ -159,8 +170,8 @@ 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: 939
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
- template: jinja
|
- template: jinja
|
||||||
- show_changes: False
|
- show_changes: False
|
||||||
@@ -173,6 +184,8 @@ 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 %}
|
||||||
@@ -204,9 +217,11 @@ 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 2>&1'
|
- name: '/usr/sbin/so-status -j > /opt/so/log/sostatus/status.log.tmp 2>&1; mv -f /opt/so/log/sostatus/status.log.tmp /opt/so/log/sostatus/status.log'
|
||||||
- identifier: so-status_check_cron
|
- identifier: so-status_check_cron
|
||||||
- user: root
|
- user: root
|
||||||
- minute: '*/1'
|
- minute: '*/1'
|
||||||
|
|||||||
@@ -18,47 +18,61 @@ 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
|
||||||
- preserve: True
|
- user: root
|
||||||
|
- 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
|
||||||
- preserve: True
|
- user: root
|
||||||
|
- 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
|
||||||
- preserve: True
|
- user: root
|
||||||
|
- 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
|
||||||
- preserve: True
|
- user: root
|
||||||
|
- 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
|
||||||
- preserve: True
|
- user: root
|
||||||
|
- 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
|
||||||
- preserve: True
|
- user: root
|
||||||
|
- 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
|
||||||
- preserve: True
|
- user: root
|
||||||
|
- 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.
|
||||||
@@ -67,46 +81,60 @@ 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
|
||||||
- preserve: True
|
- user: root
|
||||||
|
- 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
|
||||||
- preserve: True
|
- user: root
|
||||||
|
- 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
|
||||||
- preserve: True
|
- user: root
|
||||||
|
- 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
|
||||||
- preserve: True
|
- user: root
|
||||||
|
- 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
|
||||||
- preserve: True
|
- user: root
|
||||||
|
- 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
|
||||||
- preserve: True
|
- user: root
|
||||||
|
- 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
|
||||||
- preserve: True
|
- user: root
|
||||||
|
- group: root
|
||||||
|
- mode: 755
|
||||||
@@ -240,7 +240,8 @@ 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[@]}"
|
||||||
chown -R socore:socore $DEFAULT_SALT_DIR/
|
# Root-executed code; SOC only needs to read it. Local dirs stay socore-owned.
|
||||||
|
chown -R root:root $DEFAULT_SALT_DIR/
|
||||||
cd /tmp
|
cd /tmp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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
|
||||||
@@ -17,6 +18,21 @@ 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'
|
||||||
@@ -34,8 +50,7 @@ def check_needs_restarted():
|
|||||||
else:
|
else:
|
||||||
fail("Unsupported OS")
|
fail("Unsupported OS")
|
||||||
|
|
||||||
with open(outfile, 'w') as f:
|
write_atomic(outfile, val)
|
||||||
f.write(val)
|
|
||||||
|
|
||||||
def check_for_fps():
|
def check_for_fps():
|
||||||
feat = 'fps'
|
feat = 'fps'
|
||||||
@@ -56,8 +71,7 @@ def check_for_fps():
|
|||||||
# Unknown, so assume 0
|
# Unknown, so assume 0
|
||||||
fps = 0
|
fps = 0
|
||||||
|
|
||||||
with open('/opt/so/log/sostatus/fps_enabled', 'w') as f:
|
write_atomic('/opt/so/log/sostatus/fps_enabled', fps)
|
||||||
f.write(str(fps))
|
|
||||||
|
|
||||||
def check_for_lks():
|
def check_for_lks():
|
||||||
feat = 'Lks'
|
feat = 'Lks'
|
||||||
@@ -80,8 +94,7 @@ def check_for_lks():
|
|||||||
lks = 1
|
lks = 1
|
||||||
if lks:
|
if lks:
|
||||||
break
|
break
|
||||||
with open('/opt/so/log/sostatus/lks_enabled', 'w') as f:
|
write_atomic('/opt/so/log/sostatus/lks_enabled', lks)
|
||||||
f.write(str(lks))
|
|
||||||
|
|
||||||
def fail(msg):
|
def fail(msg):
|
||||||
print(msg, file=sys.stderr)
|
print(msg, file=sys.stderr)
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ if [[ $EXCLUDE_FALSE_POSITIVE_ERRORS == 'Y' ]]; then
|
|||||||
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Unexpected authorization header" # 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|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|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,7 +241,7 @@ if [[ $EXCLUDE_KNOWN_ERRORS == 'Y' ]]; then
|
|||||||
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-.*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|manifest unknown" # appears in so-dockerregistry log for so-tcpreplay following docker upgrade to 29.2.1-1
|
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|manifest unknown" # so-dockerregistry logs a tag lookup miss during image copy; not tied to one docker version
|
||||||
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
|
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
|
||||||
|
|
||||||
|
|||||||
@@ -8,21 +8,37 @@
|
|||||||
# Elastic License 2.0.
|
# Elastic License 2.0.
|
||||||
|
|
||||||
|
|
||||||
SENSOR_DIR='/nsm'
|
SENSOR_DIR="${SENSOR_DIR:-/nsm}"
|
||||||
CRIT_DISK_USAGE=90
|
CRIT_DISK_USAGE=90
|
||||||
CUR_USAGE=$(df -P $SENSOR_DIR | tail -1 | awk '{print $5}' | tr -d %)
|
LOG="${LOG:-/opt/so/log/sensor_clean.log}"
|
||||||
LOG="/opt/so/log/sensor_clean.log"
|
LOCK="${LOCK:-/var/tmp/so-sensor-clean.lock}"
|
||||||
TODAY=$(date -u "+%Y-%m-%d")
|
MAX_PASSES=100
|
||||||
|
|
||||||
|
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 /nsm/zeek/logs/ | grep -v "current" | grep -v "stats" | grep -v "packetloss" | grep -v "zeek_clean" | sort | head -n 1)
|
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)
|
||||||
if [ -z "$OLDEST_DIR" -o "$OLDEST_DIR" == ".." -o "$OLDEST_DIR" == "." ]; then
|
if [ -n "$OLDEST_DIR" ]; then
|
||||||
echo "$(date) - No old Zeek logs available to clean up in /nsm/zeek/logs/" >>$LOG
|
log "Removing directory: $ZEEK_LOGS/$OLDEST_DIR"
|
||||||
#exit 0
|
rm -rf "$ZEEK_LOGS/$OLDEST_DIR"
|
||||||
else
|
REMOVED=1
|
||||||
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
|
||||||
@@ -43,58 +59,73 @@ clean() {
|
|||||||
#fi
|
#fi
|
||||||
|
|
||||||
## Clean up Zeek extracted files processed by Strelka
|
## Clean up Zeek extracted files processed by Strelka
|
||||||
STRELKA_FILES='/nsm/strelka/processed'
|
OLDEST_STRELKA=$(find "$STRELKA_FILES" -type f -printf '%T+ %p\n' 2>/dev/null | sort -n | head -n 1)
|
||||||
OLDEST_STRELKA=$(find $STRELKA_FILES -type f -printf '%T+ %p\n' | sort -n | head -n 1)
|
if [ -n "$OLDEST_STRELKA" ]; then
|
||||||
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)
|
||||||
OLDEST_STRELKA_FILE=$(echo $OLDEST_STRELKA | awk '{print $2}')
|
log "Removing extracted files for $OLDEST_STRELKA_DATE"
|
||||||
echo "$(date) - Removing extracted files for $OLDEST_STRELKA_DATE" >>$LOG
|
REMOVED=1
|
||||||
find $STRELKA_FILES -type f -printf '%T+ %p\n' | grep $OLDEST_STRELKA_DATE | awk '{print $2}' | while read FILE; do
|
find "$STRELKA_FILES" -type f -printf '%T+ %p\n' 2>/dev/null | grep $OLDEST_STRELKA_DATE | awk '{print $2}' | while read FILE; do
|
||||||
echo "$(date) - Removing file: $FILE" >>$LOG
|
log "Removing file: $FILE"
|
||||||
rm -f "$FILE"
|
rm -f "$FILE"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
## Clean up Suricata log files
|
## Clean up Suricata log files
|
||||||
SURICATA_LOGS='/nsm/suricata'
|
OLDEST_SURICATA=$(find "$SURICATA_LOGS" -type f -printf '%T+ %p\n' 2>/dev/null | sort -n | head -n 1)
|
||||||
OLDEST_SURICATA=$(find $SURICATA_LOGS -type f -printf '%T+ %p\n' | sort -n | head -n 1)
|
if [ -n "$OLDEST_SURICATA" ]; then
|
||||||
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)
|
||||||
OLDEST_SURICATA_FILE=$(echo $OLDEST_SURICATA | awk '{print $2}')
|
log "Removing logs for $OLDEST_SURICATA_DATE"
|
||||||
echo "$(date) - Removing logs for $OLDEST_SURICATA_DATE" >>$LOG
|
REMOVED=1
|
||||||
find $SURICATA_LOGS -type f -printf '%T+ %p\n' | grep $OLDEST_SURICATA_DATE | awk '{print $2}' | while read FILE; do
|
find "$SURICATA_LOGS" -type f -printf '%T+ %p\n' 2>/dev/null | grep $OLDEST_SURICATA_DATE | awk '{print $2}' | while read FILE; do
|
||||||
echo "$(date) - Removing file: $FILE" >>$LOG
|
log "Removing file: $FILE"
|
||||||
rm -f "$FILE"
|
rm -f "$FILE"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
## Clean up extracted pcaps
|
## Clean up extracted pcaps
|
||||||
PCAPS='/nsm/pcapout'
|
OLDEST_PCAP=$(find "$PCAPS" -type f -printf '%T+ %p\n' 2>/dev/null | sort -n | head -n 1)
|
||||||
OLDEST_PCAP=$(find $PCAPS -type f -printf '%T+ %p\n' | sort -n | head -n 1)
|
if [ -n "$OLDEST_PCAP" ]; then
|
||||||
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)
|
||||||
OLDEST_PCAP_FILE=$(echo $OLDEST_PCAP | awk '{print $2}')
|
log "Removing extracted files for $OLDEST_PCAP_DATE"
|
||||||
echo "$(date) - Removing extracted files for $OLDEST_PCAP_DATE" >>$LOG
|
REMOVED=1
|
||||||
find $PCAPS -type f -printf '%T+ %p\n' | grep $OLDEST_PCAP_DATE | awk '{print $2}' | while read FILE; do
|
find "$PCAPS" -type f -printf '%T+ %p\n' 2>/dev/null | grep $OLDEST_PCAP_DATE | awk '{print $2}' | while read FILE; do
|
||||||
echo "$(date) - Removing file: $FILE" >>$LOG
|
log "Removing file: $FILE"
|
||||||
rm -f "$FILE"
|
rm -f "$FILE"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check to see if we are already running
|
# Only one instance at a time; the lock is the fd, so it releases on any exit
|
||||||
NUM_RUNNING=$(pgrep -cf "/bin/bash /usr/sbin/so-sensor-clean")
|
exec 9>"$LOCK" || exit 1
|
||||||
[ "$NUM_RUNNING" -gt 1 ] && echo "$(date) - $NUM_RUNNING sensor clean script processes running...exiting." >>$LOG && exit 0
|
if ! flock -n 9; then
|
||||||
|
log "another so-sensor-clean is already running (lock $LOCK held); exiting"
|
||||||
if [ "$CUR_USAGE" -gt "$CRIT_DISK_USAGE" ]; then
|
exit 0
|
||||||
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
|
||||||
@@ -125,4 +125,6 @@ else
|
|||||||
RAIDSTATUS=1
|
RAIDSTATUS=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "nsmraid=$RAIDSTATUS" > /opt/so/log/raid/status.log
|
# 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.tmp
|
||||||
|
mv -f /opt/so/log/raid/status.log.tmp /opt/so/log/raid/status.log
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
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
|
||||||
@@ -58,18 +64,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: []
|
||||||
@@ -128,6 +134,7 @@ 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: []
|
||||||
|
|||||||
@@ -1,8 +1,26 @@
|
|||||||
{% 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('.') %}
|
|
||||||
{% set FIRSTTHREE = RANGESPLIT[0] ~ '.' ~ RANGESPLIT[1] ~ '.' ~ RANGESPLIT[2] ~ '.' %}
|
{% if DOCKERMERGED.networks.sobridge is not mapping %}
|
||||||
|
{% 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() %}
|
||||||
{% do DOCKERMERGED.containers[container].update({'ip': FIRSTTHREE ~ DOCKERMERGED.containers[container].final_octet}) %}
|
{% set CONTAINER_NETS = vals.get('networks', ['sobridge']) %}
|
||||||
|
{% 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 %}
|
||||||
+14
-10
@@ -18,10 +18,10 @@ dockergroup:
|
|||||||
dockerheldpackages:
|
dockerheldpackages:
|
||||||
pkg.installed:
|
pkg.installed:
|
||||||
- pkgs:
|
- pkgs:
|
||||||
- containerd.io: 2.2.1-1.el9
|
- containerd.io: 2.3.6-1.el9
|
||||||
- docker-ce: 3:29.2.1-1.el9
|
- docker-ce: 3:29.8.1-1.el9
|
||||||
- docker-ce-cli: 1:29.2.1-1.el9
|
- docker-ce-cli: 1:29.8.1-1.el9
|
||||||
- docker-ce-rootless-extras: 29.2.1-1.el9
|
- docker-ce-rootless-extras: 29.8.1-1.el9
|
||||||
- hold: True
|
- hold: True
|
||||||
- update_holds: True
|
- update_holds: True
|
||||||
|
|
||||||
@@ -71,15 +71,19 @@ 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
|
||||||
|
|
||||||
sos_docker_net:
|
{% for NETNAME, NETWORK in DOCKERMERGED.networks.items() %}
|
||||||
|
{% if not NETWORK.get('manager_only') or GLOBALS.get('is_manager', False) %}
|
||||||
|
sos_docker_net_{{ NETNAME }}:
|
||||||
docker_network.present:
|
docker_network.present:
|
||||||
- name: sobridge
|
- name: {{ NETNAME }}
|
||||||
- subnet: {{ DOCKERMERGED.range }}
|
- subnet: {{ NETWORK.range }}
|
||||||
- gateway: {{ DOCKERMERGED.gateway }}
|
- gateway: {{ NETWORK.gateway }}
|
||||||
- options:
|
- options:
|
||||||
com.docker.network.bridge.name: 'sobridge'
|
com.docker.network.bridge.name: '{{ NETNAME }}'
|
||||||
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 sobridge
|
- unless: ip l | grep {{ NETNAME }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
@@ -7,6 +7,40 @@ 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.
|
||||||
@@ -34,6 +68,16 @@ 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
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ 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:
|
||||||
@@ -33,8 +34,8 @@ elastalert_sbin:
|
|||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://elastalert/tools/sbin
|
- source: salt://elastalert/tools/sbin
|
||||||
- user: 933
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
|
|
||||||
#elastalert_sbin_jinja:
|
#elastalert_sbin_jinja:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ elastalert:
|
|||||||
buffer_time:
|
buffer_time:
|
||||||
minutes: 10
|
minutes: 10
|
||||||
old_query_limit:
|
old_query_limit:
|
||||||
minutes: 5
|
minutes: 1440
|
||||||
es_port: 9200
|
es_port: 9200
|
||||||
es_conn_timeout: 55
|
es_conn_timeout: 55
|
||||||
max_query_size: 5000
|
max_query_size: 5000
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import types
|
||||||
|
|
||||||
|
# stand-ins when ElastAlert isn't installed (CI)
|
||||||
|
try:
|
||||||
|
import elastalert.alerts # noqa: F401
|
||||||
|
except ImportError:
|
||||||
|
|
||||||
|
class Alerter:
|
||||||
|
def __init__(self, rule):
|
||||||
|
self.rule = rule
|
||||||
|
|
||||||
|
class DateTimeEncoder(json.JSONEncoder):
|
||||||
|
def default(self, obj):
|
||||||
|
return obj.isoformat() if hasattr(obj, 'isoformat') else json.JSONEncoder.default(self, obj)
|
||||||
|
|
||||||
|
class EAException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def lookup_es_key(doc, term):
|
||||||
|
for part in term.split('.'):
|
||||||
|
if not isinstance(doc, dict) or part not in doc:
|
||||||
|
return None
|
||||||
|
doc = doc[part]
|
||||||
|
return doc
|
||||||
|
|
||||||
|
def ts_to_dt(value):
|
||||||
|
return value if isinstance(value, datetime) else datetime.fromisoformat(value)
|
||||||
|
|
||||||
|
def elasticsearch_client(conf):
|
||||||
|
return None # tests set the alerter's client
|
||||||
|
|
||||||
|
alerts = types.ModuleType('elastalert.alerts')
|
||||||
|
alerts.Alerter = Alerter
|
||||||
|
alerts.DateTimeEncoder = DateTimeEncoder
|
||||||
|
util = types.ModuleType('elastalert.util')
|
||||||
|
util.EAException = EAException
|
||||||
|
util.elastalert_logger = logging.getLogger('elastalert')
|
||||||
|
util.lookup_es_key = lookup_es_key
|
||||||
|
util.ts_to_dt = ts_to_dt
|
||||||
|
util.elasticsearch_client = elasticsearch_client
|
||||||
|
package = types.ModuleType('elastalert')
|
||||||
|
package.alerts = alerts
|
||||||
|
package.util = util
|
||||||
|
sys.modules.update({'elastalert': package, 'elastalert.alerts': alerts, 'elastalert.util': util})
|
||||||
|
|
||||||
|
# stand-ins when elasticsearch-py isn't installed (CI)
|
||||||
|
try:
|
||||||
|
import elasticsearch.exceptions # noqa: F401
|
||||||
|
except ImportError:
|
||||||
|
|
||||||
|
class ElasticsearchException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TransportError(ElasticsearchException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ConnectionError(TransportError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ConflictError(TransportError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class RequestError(TransportError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
exceptions = types.ModuleType('elasticsearch.exceptions')
|
||||||
|
for cls in (ElasticsearchException, TransportError, ConnectionError, ConflictError, RequestError):
|
||||||
|
setattr(exceptions, cls.__name__, cls)
|
||||||
|
es_package = types.ModuleType('elasticsearch')
|
||||||
|
es_package.exceptions = exceptions
|
||||||
|
sys.modules.update({'elasticsearch': es_package, 'elasticsearch.exceptions': exceptions})
|
||||||
@@ -1,63 +1,269 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
|
# 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
|
# 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
|
# https://securityonion.net/license; you may not use this file except in compliance with the
|
||||||
# Elastic License 2.0.
|
# Elastic License 2.0.
|
||||||
|
|
||||||
|
from datetime import datetime, timezone
|
||||||
from time import gmtime, strftime
|
import hashlib
|
||||||
import requests,json
|
import ipaddress
|
||||||
from elastalert.alerts import Alerter
|
import json
|
||||||
|
import re
|
||||||
|
import uuid
|
||||||
|
|
||||||
import urllib3
|
import urllib3
|
||||||
|
from elasticsearch.exceptions import ConflictError, ElasticsearchException, RequestError
|
||||||
|
from elastalert.alerts import Alerter, DateTimeEncoder
|
||||||
|
from elastalert.util import EAException, elastalert_logger, elasticsearch_client, lookup_es_key, ts_to_dt
|
||||||
|
|
||||||
|
# grid runs verify_certs: false; also quiets ElastAlert's own queries
|
||||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
|
||||||
|
ALERT_INDEX = 'logs-detections.alerts-so'
|
||||||
|
# ES error text kept in logs and alerts
|
||||||
|
ERROR_TEXT_LIMIT = 500
|
||||||
|
# a match missing backend columns (window_start, count, @timestamp); the alert is still written
|
||||||
|
MATCH_ERRORS = (KeyError, TypeError, ValueError)
|
||||||
|
|
||||||
|
|
||||||
class SecurityOnionESAlerter(Alerter):
|
class SecurityOnionESAlerter(Alerter):
|
||||||
"""
|
"""
|
||||||
Use matched data to create alerts in Elasticsearch.
|
Use matched data to create alerts in Elasticsearch.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
required_options = set(['detection_title', 'sigma_level'])
|
required_options = {'detection_title', 'sigma_level'}
|
||||||
optional_fields = ['sigma_category', 'sigma_product', 'sigma_service']
|
optional_fields = ['sigma_category', 'sigma_product', 'sigma_service', 'sigma_correlation']
|
||||||
|
|
||||||
|
# count column and default summary per type; stored alert data, so not localized
|
||||||
|
CORRELATION_COUNTS = {
|
||||||
|
'event_count': ('event_count', '%count% events'),
|
||||||
|
'value_count': ('value_count', '%count% distinct values'),
|
||||||
|
'temporal': ('event_type_count', '%count% correlated rules matched'),
|
||||||
|
'value_sum': ('value_sum', 'total %count%'),
|
||||||
|
'value_avg': ('value_avg', 'average %count%'),
|
||||||
|
'value_percentile': ('value_percentile', 'percentile %count%'),
|
||||||
|
'value_median': ('value_median', 'median %count%'),
|
||||||
|
}
|
||||||
|
PLACEHOLDER = re.compile(r'%([^%\s]+)%')
|
||||||
|
# group-by fields copied into ECS related.*
|
||||||
|
RELATED_USERS = {'user.name', 'winlog.event_data.TargetUserName', 'winlog.event_data.SubjectUserName'}
|
||||||
|
RELATED_HOSTS = {'host.name', 'host.hostname', 'winlog.computer_name'}
|
||||||
|
|
||||||
|
def __init__(self, rule):
|
||||||
|
super().__init__(rule)
|
||||||
|
# uses the grid's TLS, auth and timeout settings
|
||||||
|
self.es = elasticsearch_client(rule)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_correlation(self):
|
||||||
|
return bool(self.rule.get('sigma_correlation'))
|
||||||
|
|
||||||
|
def query_keys(self):
|
||||||
|
"""compound_query_key holds the list; query_key is flattened to a string."""
|
||||||
|
if self.rule.get('compound_query_key'):
|
||||||
|
return self.rule['compound_query_key']
|
||||||
|
if self.rule.get('query_key'):
|
||||||
|
return [self.rule['query_key']]
|
||||||
|
return []
|
||||||
|
|
||||||
|
def alert_id(self, match):
|
||||||
|
"""Stable id: window end + group values for correlations, source _id otherwise; random without one."""
|
||||||
|
if self.is_correlation:
|
||||||
|
# ungrouped rows have a hashed _id that changes with the count
|
||||||
|
values = ''.join(f"|{lookup_es_key(match, k)}" for k in self.query_keys())
|
||||||
|
key = f"{self.rule['detection_public_id']}|{ts_to_dt(match['@timestamp']).isoformat()}{values}"
|
||||||
|
elif match.get('_id'):
|
||||||
|
key = f"{self.rule['detection_public_id']}|{match['_id']}"
|
||||||
|
else:
|
||||||
|
return uuid.uuid4().hex
|
||||||
|
|
||||||
|
return hashlib.sha256(key.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
def group(self, match):
|
||||||
|
"""Group-by values, joined like ElastAlert's realert key."""
|
||||||
|
return ', '.join(str(lookup_es_key(match, k)) for k in self.query_keys())
|
||||||
|
|
||||||
|
def related_bucket(self, key):
|
||||||
|
if key == 'ip' or key.endswith('.ip'):
|
||||||
|
return 'ip'
|
||||||
|
if key in self.RELATED_USERS or key.endswith('.user.name'):
|
||||||
|
return 'user'
|
||||||
|
if key in self.RELATED_HOSTS:
|
||||||
|
return 'hosts'
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def valid_ip(value):
|
||||||
|
try:
|
||||||
|
ipaddress.ip_address(value)
|
||||||
|
return True
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def related(self, match):
|
||||||
|
"""ECS related.* from group-by values; skips invalid IPs."""
|
||||||
|
related = {}
|
||||||
|
for key in self.query_keys():
|
||||||
|
bucket = self.related_bucket(key)
|
||||||
|
if not bucket:
|
||||||
|
continue
|
||||||
|
# original spellings of a lowercased group
|
||||||
|
value = lookup_es_key(match, f"{key}_spellings")
|
||||||
|
if value is None:
|
||||||
|
value = lookup_es_key(match, key)
|
||||||
|
for v in value if isinstance(value, list) else [value]:
|
||||||
|
if v is None or (bucket == 'ip' and not self.valid_ip(str(v))):
|
||||||
|
continue
|
||||||
|
# dict: ordered and deduped
|
||||||
|
related.setdefault(bucket, {})[str(v)] = None
|
||||||
|
return {bucket: list(values) for bucket, values in related.items()}
|
||||||
|
|
||||||
|
def event_data(self, match):
|
||||||
|
"""The match minus the compound query_key field, which ES would map by its last part."""
|
||||||
|
if not self.rule.get('compound_query_key'):
|
||||||
|
return match
|
||||||
|
return {k: v for k, v in match.items() if k != self.rule['query_key']}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_value(value):
|
||||||
|
if isinstance(value, list):
|
||||||
|
shown = ', '.join(str(v) for v in value[:3])
|
||||||
|
return shown if len(value) <= 3 else f"{shown} and {len(value) - 3} more"
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_count(value):
|
||||||
|
if isinstance(value, float) and not value.is_integer():
|
||||||
|
return f"{value:,.2f}"
|
||||||
|
if isinstance(value, (int, float)):
|
||||||
|
return f"{int(value):,}"
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_duration(seconds):
|
||||||
|
for unit, size in (('hour', 3600), ('minute', 60)):
|
||||||
|
if seconds >= 2 * size:
|
||||||
|
return f"{seconds // size} {unit}s"
|
||||||
|
return f"{seconds} second{'' if seconds == 1 else 's'}"
|
||||||
|
|
||||||
|
def summary(self, match):
|
||||||
|
"""One-line correlation summary."""
|
||||||
|
column, label = self.CORRELATION_COUNTS.get(self.rule['sigma_correlation'], (None, '%count%'))
|
||||||
|
start = ts_to_dt(match['window_start'])
|
||||||
|
end = ts_to_dt(match['@timestamp'])
|
||||||
|
values = {
|
||||||
|
'count': self.format_count(match.get(column)),
|
||||||
|
'start': start.strftime('%Y-%m-%d %H:%M:%S UTC'),
|
||||||
|
'end': end.strftime('%Y-%m-%d %H:%M:%S UTC'),
|
||||||
|
'duration': self.format_duration(int((end - start).total_seconds())),
|
||||||
|
}
|
||||||
|
|
||||||
|
template = self.rule.get('summary_template')
|
||||||
|
if not template:
|
||||||
|
groups = ', '.join(f"{k} %{k}%" for k in self.query_keys())
|
||||||
|
template = f"{label} for {groups} in %duration%" if groups else f"{label} in %duration%"
|
||||||
|
|
||||||
|
def fill(m):
|
||||||
|
if m[1] in values:
|
||||||
|
return values[m[1]]
|
||||||
|
value = lookup_es_key(match, m[1])
|
||||||
|
# unknown placeholders stay visible so typos show
|
||||||
|
return m[0] if value is None else self.format_value(value)
|
||||||
|
|
||||||
|
return self.PLACEHOLDER.sub(fill, template)
|
||||||
|
|
||||||
def alert(self, matches):
|
def alert(self, matches):
|
||||||
for match in matches:
|
for match in matches:
|
||||||
timestamp = strftime("%Y-%m-%d"'T'"%H:%M:%S"'.000Z', gmtime())
|
try:
|
||||||
headers = {"Content-Type": "application/json"}
|
alert_id = self.alert_id(match)
|
||||||
|
except MATCH_ERRORS as e:
|
||||||
|
elastalert_logger.warning("Writing alert for rule %s without a stable id, so a retry may duplicate it: %r",
|
||||||
|
self.rule['detection_public_id'], e)
|
||||||
|
alert_id = uuid.uuid4().hex
|
||||||
|
try:
|
||||||
|
self.write(alert_id, self.payload(match))
|
||||||
|
except ElasticsearchException as e:
|
||||||
|
# EAException makes ElastAlert retry
|
||||||
|
raise EAException(f"Unable to write the alert to Elasticsearch: {str(e)[:ERROR_TEXT_LIMIT]}") from e
|
||||||
|
|
||||||
creds = None
|
def payload(self, match):
|
||||||
if 'es_username' in self.rule and 'es_password' in self.rule:
|
rule_info = {
|
||||||
creds = (self.rule['es_username'], self.rule['es_password'])
|
"name": self.rule['detection_title'],
|
||||||
|
"uuid": self.rule['detection_public_id']
|
||||||
|
}
|
||||||
|
|
||||||
# Start building the rule dict
|
# Add optional fields if they are present in the rule
|
||||||
rule_info = {
|
for field in self.optional_fields:
|
||||||
"name": self.rule['detection_title'],
|
rule_key = field.split('_')[-1] # Assumes field format "sigma_<key>"
|
||||||
"uuid": self.rule['detection_public_id']
|
if field in self.rule:
|
||||||
}
|
rule_info[rule_key] = self.rule[field]
|
||||||
|
|
||||||
# Add optional fields if they are present in the rule
|
event_info = {
|
||||||
for field in self.optional_fields:
|
"kind": "alert",
|
||||||
rule_key = field.split('_')[-1] # Assumes field format "sigma_<key>"
|
"severity": self.rule['event.severity'],
|
||||||
if field in self.rule:
|
"module": self.rule['event.module'],
|
||||||
rule_info[rule_key] = self.rule[field]
|
"dataset": self.rule['event.dataset'],
|
||||||
|
"severity_label": self.rule['sigma_level']
|
||||||
|
}
|
||||||
|
|
||||||
# Construct the payload with the conditional rule_info
|
payload = {
|
||||||
payload = {
|
"tags": ["alert"],
|
||||||
"tags": "alert",
|
"rule": rule_info,
|
||||||
"rule": rule_info,
|
"event": event_info,
|
||||||
"event": {
|
"sigma_level": self.rule['sigma_level'],
|
||||||
"severity": self.rule['event.severity'],
|
"event_data": self.event_data(match),
|
||||||
"module": self.rule['event.module'],
|
"@timestamp": datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.000Z')
|
||||||
"dataset": self.rule['event.dataset'],
|
}
|
||||||
"severity_label": self.rule['sigma_level']
|
|
||||||
},
|
if self.is_correlation:
|
||||||
"sigma_level": self.rule['sigma_level'],
|
keys = self.query_keys()
|
||||||
"event_data": match,
|
try:
|
||||||
"@timestamp": timestamp
|
# built before any is added, so a failure adds none
|
||||||
}
|
reason = self.summary(match)
|
||||||
url = f"https://{self.rule['es_host']}:{self.rule['es_port']}/logs-detections.alerts-so/_doc/"
|
labels = {"correlation_group_by": ', '.join(keys), "correlation_group": self.group(match)} if keys else None
|
||||||
requests.post(url, data=json.dumps(payload), headers=headers, verify=False, auth=creds)
|
related = self.related(match)
|
||||||
|
except MATCH_ERRORS as e:
|
||||||
|
elastalert_logger.warning("Writing alert for rule %s without its correlation summary: %r",
|
||||||
|
self.rule['detection_public_id'], e)
|
||||||
|
else:
|
||||||
|
payload["event"]["reason"] = reason
|
||||||
|
if labels:
|
||||||
|
payload["labels"] = labels
|
||||||
|
if related:
|
||||||
|
payload["related"] = related
|
||||||
|
|
||||||
|
return payload
|
||||||
|
|
||||||
|
def write(self, alert_id, payload):
|
||||||
|
try:
|
||||||
|
self.create(alert_id, payload)
|
||||||
|
except RequestError as e:
|
||||||
|
# mapping rejections come from event_data; retry it as text
|
||||||
|
rejection = str(e)[:ERROR_TEXT_LIMIT]
|
||||||
|
try:
|
||||||
|
self.create(alert_id, self.without_event_data(payload, rejection))
|
||||||
|
except RequestError as again:
|
||||||
|
elastalert_logger.error("Dropping alert %s for rule %s, rejected by Elasticsearch even without its event data: %s; first rejection: %s",
|
||||||
|
alert_id, self.rule['detection_public_id'], str(again)[:ERROR_TEXT_LIMIT], rejection)
|
||||||
|
return
|
||||||
|
elastalert_logger.warning("Stored alert %s for rule %s with its event data as text, rejected by Elasticsearch: %s",
|
||||||
|
alert_id, self.rule['detection_public_id'], rejection)
|
||||||
|
|
||||||
|
def create(self, alert_id, payload):
|
||||||
|
try:
|
||||||
|
self.es.create(index=ALERT_INDEX, id=alert_id, body=payload)
|
||||||
|
except ConflictError:
|
||||||
|
pass # a repeat id is already stored
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def without_event_data(payload, rejection):
|
||||||
|
"""Moves event_data to event.original; the tag keeps Fleet's final pipeline from dropping it."""
|
||||||
|
fallback = {k: v for k, v in payload.items() if k != 'event_data'}
|
||||||
|
fallback['event'] = dict(payload['event'], original=json.dumps(payload['event_data'], cls=DateTimeEncoder))
|
||||||
|
fallback['error'] = {'message': f"event_data rejected by Elasticsearch: {rejection}"}
|
||||||
|
fallback['tags'] = payload['tags'] + ['preserve_original_event']
|
||||||
|
return fallback
|
||||||
|
|
||||||
def get_info(self):
|
def get_info(self):
|
||||||
return {'type': 'SecurityOnionESAlerter'}
|
return {'type': 'SecurityOnionESAlerter'}
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
# 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 copy
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
import importlib.util
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from elasticsearch.exceptions import ConflictError, ConnectionError, RequestError
|
||||||
|
|
||||||
|
spec = importlib.util.spec_from_file_location('securityonion_es', os.path.join(os.path.dirname(__file__), 'securityonion-es.py'))
|
||||||
|
es = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(es)
|
||||||
|
|
||||||
|
BASE_RULE = {
|
||||||
|
'name': 'Many Failed Network Logons To One Host From One Source -- 35a42db6-6629-45af-b8aa-e1fa33c28ef5',
|
||||||
|
'detection_title': 'Many Failed Network Logons To One Host From One Source',
|
||||||
|
'detection_public_id': '35a42db6-6629-45af-b8aa-e1fa33c28ef5',
|
||||||
|
'sigma_level': 'medium',
|
||||||
|
'sigma_correlation': 'event_count',
|
||||||
|
'event.severity': 3,
|
||||||
|
'event.module': 'sigma',
|
||||||
|
'event.dataset': 'sigma.alert',
|
||||||
|
'es_host': 'manager',
|
||||||
|
'es_port': 9200,
|
||||||
|
'es_conn_timeout': 55,
|
||||||
|
'summary_template': '%count% failed network logons to %host.name% from %source.ip% in %duration%',
|
||||||
|
}
|
||||||
|
|
||||||
|
PLAIN_RULE = {k: v for k, v in BASE_RULE.items() if k not in ('sigma_correlation', 'summary_template')}
|
||||||
|
|
||||||
|
|
||||||
|
def correlation_match():
|
||||||
|
return {
|
||||||
|
'event_count': 3561,
|
||||||
|
'window_start': '2026-09-30T18:05:10+00:00',
|
||||||
|
'@timestamp': '2026-09-30T18:07:53+00:00',
|
||||||
|
'host': {'name': 'host-01'},
|
||||||
|
'source': {'ip': '192.0.2.10'},
|
||||||
|
'_id': '6d1c',
|
||||||
|
'num_hits': 1,
|
||||||
|
'num_matches': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestSecurityOnionESAlerter(unittest.TestCase):
|
||||||
|
|
||||||
|
def creates(self, rule, match, effects=None):
|
||||||
|
"""Run alert(); return (body, id) of each create."""
|
||||||
|
alerter = es.SecurityOnionESAlerter(rule)
|
||||||
|
alerter.es = MagicMock()
|
||||||
|
alerter.es.create.side_effect = effects
|
||||||
|
alerter.alert([match])
|
||||||
|
calls = alerter.es.create.call_args_list
|
||||||
|
self.assertTrue(all(c.kwargs['index'] == 'logs-detections.alerts-so' for c in calls))
|
||||||
|
# as the client serializes it
|
||||||
|
return [(json.loads(json.dumps(c.kwargs['body'], cls=es.DateTimeEncoder)), c.kwargs['id']) for c in calls]
|
||||||
|
|
||||||
|
def send(self, rule, match):
|
||||||
|
"""Run alert(); return the payload it wrote and its id."""
|
||||||
|
(payload, alert_id), = self.creates(rule, match)
|
||||||
|
return payload, alert_id
|
||||||
|
|
||||||
|
def test_compound_query_key_left_out_of_event_data(self):
|
||||||
|
rule = dict(BASE_RULE, compound_query_key=['host.name', 'source.ip'], query_key='host.name,source.ip')
|
||||||
|
match = correlation_match()
|
||||||
|
match['host.name,source.ip'] = 'host-01, 192.0.2.10'
|
||||||
|
original = copy.deepcopy(match)
|
||||||
|
|
||||||
|
payload, _ = self.send(rule, match)
|
||||||
|
|
||||||
|
self.assertNotIn('host.name,source.ip', payload['event_data'])
|
||||||
|
self.assertEqual(payload['event_data']['host'], {'name': 'host-01'})
|
||||||
|
self.assertEqual(payload['event_data']['source'], {'ip': '192.0.2.10'})
|
||||||
|
self.assertEqual(payload['labels'], {'correlation_group_by': 'host.name, source.ip', 'correlation_group': 'host-01, 192.0.2.10'})
|
||||||
|
self.assertEqual(payload['related'], {'hosts': ['host-01'], 'ip': ['192.0.2.10']})
|
||||||
|
self.assertEqual(payload['event']['kind'], 'alert')
|
||||||
|
self.assertEqual(payload['event']['reason'], '3,561 failed network logons to host-01 from 192.0.2.10 in 2 minutes')
|
||||||
|
# ElastAlert reuses the match
|
||||||
|
self.assertEqual(match, original)
|
||||||
|
|
||||||
|
def test_single_query_key(self):
|
||||||
|
rule = dict(BASE_RULE, query_key='user.name', summary_template='%count% failed SOC logins for %user.name%')
|
||||||
|
match = {'event_count': 3, 'window_start': '2026-09-30T16:49:52+00:00', '@timestamp': '2026-09-30T16:50:00+00:00',
|
||||||
|
'user': {'name': 'user@example.invalid'}}
|
||||||
|
|
||||||
|
payload, _ = self.send(rule, match)
|
||||||
|
|
||||||
|
self.assertEqual(payload['labels'], {'correlation_group_by': 'user.name', 'correlation_group': 'user@example.invalid'})
|
||||||
|
self.assertEqual(payload['related'], {'user': ['user@example.invalid']})
|
||||||
|
self.assertEqual(payload['event']['reason'], '3 failed SOC logins for user@example.invalid')
|
||||||
|
self.assertEqual(payload['event_data'], match)
|
||||||
|
|
||||||
|
def test_related_buckets(self):
|
||||||
|
rule = dict(BASE_RULE, compound_query_key=['winlog.event_data.TargetUserName', 'source.ip', 'dns.highest_registered_domain'],
|
||||||
|
query_key='winlog.event_data.TargetUserName,source.ip,dns.highest_registered_domain')
|
||||||
|
match = correlation_match()
|
||||||
|
match.update({'winlog': {'event_data': {'TargetUserName': ['admin1', 'svc', 'admin1']}},
|
||||||
|
'source': {'ip': 'not-an-ip'}, 'dns': {'highest_registered_domain': 'example.com'}})
|
||||||
|
|
||||||
|
payload, _ = self.send(rule, match)
|
||||||
|
|
||||||
|
# deduped; invalid IP skipped; domain stays in the group only
|
||||||
|
self.assertEqual(payload['related'], {'user': ['admin1', 'svc']})
|
||||||
|
self.assertEqual(payload['labels']['correlation_group'], "['admin1', 'svc', 'admin1'], not-an-ip, example.com")
|
||||||
|
|
||||||
|
payload, _ = self.send(dict(BASE_RULE, query_key='dns.highest_registered_domain'), match)
|
||||||
|
|
||||||
|
self.assertNotIn('related', payload)
|
||||||
|
|
||||||
|
def test_related_uses_original_spellings(self):
|
||||||
|
rule = dict(BASE_RULE, query_key='user.name', summary_template=None)
|
||||||
|
match = {'event_count': 3, 'window_start': '2026-09-30T16:49:52+00:00', '@timestamp': '2026-09-30T16:50:00+00:00',
|
||||||
|
'user': {'name': 'admin', 'name_spellings': ['Admin', 'admin', 'ADMIN']}}
|
||||||
|
|
||||||
|
payload, _ = self.send(rule, match)
|
||||||
|
|
||||||
|
# the group shows the lowercased value; related.user finds every spelling
|
||||||
|
self.assertEqual(payload['labels']['correlation_group'], 'admin')
|
||||||
|
self.assertEqual(payload['related'], {'user': ['Admin', 'admin', 'ADMIN']})
|
||||||
|
self.assertEqual(payload['event']['reason'], '3 events for user.name admin in 8 seconds')
|
||||||
|
|
||||||
|
def test_plain_rule_has_no_correlation_fields(self):
|
||||||
|
# a query_key alone (e.g. from an override) isn't a correlation
|
||||||
|
rule = dict(PLAIN_RULE, query_key='user.name')
|
||||||
|
# ElastAlert parses @timestamp for EQL hits
|
||||||
|
match = {'@timestamp': datetime(2026, 9, 30, 16, 50, tzinfo=timezone.utc), '_id': 'abc',
|
||||||
|
'process': {'name': 'whoami.exe'}, 'user': {'name': 'user'}}
|
||||||
|
|
||||||
|
payload, alert_id = self.send(rule, match)
|
||||||
|
|
||||||
|
alerter = es.SecurityOnionESAlerter(rule)
|
||||||
|
self.assertNotIn('labels', payload)
|
||||||
|
self.assertNotIn('related', payload)
|
||||||
|
self.assertNotIn('reason', payload['event'])
|
||||||
|
self.assertEqual(payload['event']['kind'], 'alert')
|
||||||
|
self.assertEqual(payload['event_data'], dict(match, **{'@timestamp': '2026-09-30T16:50:00+00:00'}))
|
||||||
|
self.assertEqual(alert_id, alerter.alert_id(match))
|
||||||
|
self.assertNotEqual(alerter.alert_id(match), alerter.alert_id(dict(match, _id='abd')))
|
||||||
|
# without an _id, never deduplicated
|
||||||
|
self.assertNotEqual(alerter.alert_id({'_id': None}), alerter.alert_id({'_id': None}))
|
||||||
|
|
||||||
|
def test_ungrouped_correlation_id_ignores_row_hash(self):
|
||||||
|
rule = dict(BASE_RULE, summary_template=None)
|
||||||
|
first = {k: v for k, v in correlation_match().items() if k not in ('host', 'source')}
|
||||||
|
# ES|QL hashes the row into _id, so a later count changes it
|
||||||
|
later = dict(first, event_count=3600, _id='9f2a')
|
||||||
|
|
||||||
|
payload, alert_id = self.send(rule, first)
|
||||||
|
|
||||||
|
alerter = es.SecurityOnionESAlerter(rule)
|
||||||
|
self.assertEqual(alerter.alert_id(first), alerter.alert_id(later))
|
||||||
|
self.assertNotEqual(alerter.alert_id(first), alerter.alert_id(dict(first, **{'@timestamp': '2026-09-30T18:09:00+00:00'})))
|
||||||
|
self.assertEqual(alert_id, alerter.alert_id(first))
|
||||||
|
self.assertEqual(payload['event']['reason'], '3,561 events in 2 minutes')
|
||||||
|
self.assertNotIn('labels', payload)
|
||||||
|
|
||||||
|
def test_temporal_count_column(self):
|
||||||
|
rule = dict(BASE_RULE, sigma_correlation='temporal', summary_template=None)
|
||||||
|
match = {'event_type_count': 2, 'window_start': '2026-09-30T16:49:52+00:00', '@timestamp': '2026-09-30T16:50:00+00:00'}
|
||||||
|
|
||||||
|
payload, _ = self.send(rule, match)
|
||||||
|
|
||||||
|
self.assertEqual(payload['event']['reason'], '2 correlated rules matched in 8 seconds')
|
||||||
|
|
||||||
|
def test_grouped_correlation_id_unchanged(self):
|
||||||
|
"""Ids of alerts already written must not change."""
|
||||||
|
rule = dict(BASE_RULE, compound_query_key=['host.name', 'source.ip'], query_key='host.name,source.ip')
|
||||||
|
key = f"{BASE_RULE['detection_public_id']}|2026-09-30T18:07:53+00:00|host-01|192.0.2.10"
|
||||||
|
|
||||||
|
self.assertEqual(es.SecurityOnionESAlerter(rule).alert_id(correlation_match()), es.hashlib.sha256(key.encode()).hexdigest())
|
||||||
|
|
||||||
|
def test_rejected_event_data_is_kept_as_text(self):
|
||||||
|
rule = dict(BASE_RULE, query_key='user.name', summary_template='%count% failed SOC logins for %user.name%')
|
||||||
|
match = {'event_count': 3, 'window_start': '2026-09-30T16:49:52+00:00', '@timestamp': '2026-09-30T16:50:00+00:00',
|
||||||
|
'user': {'name': 'user@example.invalid'}}
|
||||||
|
rejected = RequestError(400, 'document_parsing_exception', {})
|
||||||
|
|
||||||
|
(first, _), (second, _) = self.creates(rule, match, [rejected, None])
|
||||||
|
|
||||||
|
self.assertIn('event_data', first)
|
||||||
|
self.assertNotIn('event_data', second)
|
||||||
|
self.assertEqual(json.loads(second['event']['original']), match)
|
||||||
|
self.assertEqual(second['tags'], ['alert', 'preserve_original_event'])
|
||||||
|
self.assertEqual(first['tags'], ['alert'])
|
||||||
|
self.assertTrue(second['error']['message'].startswith('event_data rejected by Elasticsearch: '))
|
||||||
|
self.assertIn('document_parsing_exception', second['error']['message'])
|
||||||
|
# everything else carries over
|
||||||
|
self.assertEqual({k: v for k, v in second['event'].items() if k != 'original'}, first['event'])
|
||||||
|
changed = ('event_data', 'event', 'error', 'tags')
|
||||||
|
self.assertEqual({k: v for k, v in second.items() if k not in changed}, {k: v for k, v in first.items() if k not in changed})
|
||||||
|
|
||||||
|
def test_rejected_twice_is_dropped_without_retry(self):
|
||||||
|
rejected = RequestError(400, 'document_parsing_exception', {})
|
||||||
|
match = {'@timestamp': '2026-09-30T16:50:00+00:00', '_id': 'abc'}
|
||||||
|
|
||||||
|
# no EAException, so no retry
|
||||||
|
self.assertEqual(len(self.creates(PLAIN_RULE, match, [rejected, rejected])), 2)
|
||||||
|
|
||||||
|
def test_write_failure_is_retried(self):
|
||||||
|
match = {'@timestamp': '2026-09-30T16:50:00+00:00', '_id': 'abc'}
|
||||||
|
|
||||||
|
# EAException makes ElastAlert retry the alert
|
||||||
|
with self.assertRaisesRegex(es.EAException, 'Unable to write the alert to Elasticsearch'):
|
||||||
|
self.creates(PLAIN_RULE, match, [ConnectionError('N/A', 'refused', None)])
|
||||||
|
|
||||||
|
# a repeat id is already stored
|
||||||
|
self.assertEqual(len(self.creates(PLAIN_RULE, match, [ConflictError(409, 'version_conflict_engine_exception', {})])), 1)
|
||||||
|
|
||||||
|
def test_correlation_fields_are_optional(self):
|
||||||
|
rule = dict(BASE_RULE, query_key='source.ip')
|
||||||
|
# no window_start: the summary cannot be built
|
||||||
|
match = {k: v for k, v in correlation_match().items() if k != 'window_start'}
|
||||||
|
|
||||||
|
with self.assertLogs('elastalert', 'WARNING'):
|
||||||
|
payload, _ = self.send(rule, match)
|
||||||
|
|
||||||
|
self.assertEqual(payload['event']['kind'], 'alert')
|
||||||
|
self.assertNotIn('reason', payload['event'])
|
||||||
|
self.assertNotIn('labels', payload)
|
||||||
|
self.assertNotIn('related', payload)
|
||||||
|
|
||||||
|
def test_unstable_id_still_writes(self):
|
||||||
|
# no @timestamp: the window end is unknown
|
||||||
|
match = {k: v for k, v in correlation_match().items() if k not in ('@timestamp', 'window_start')}
|
||||||
|
|
||||||
|
with self.assertLogs('elastalert', 'WARNING'):
|
||||||
|
payload, alert_id = self.send(BASE_RULE, match)
|
||||||
|
|
||||||
|
self.assertEqual(len(alert_id), 32)
|
||||||
|
self.assertEqual(payload['event_data'], match)
|
||||||
|
|
||||||
|
def test_summary_formats_values(self):
|
||||||
|
rule = dict(BASE_RULE, sigma_correlation='value_avg', query_key='source.ip',
|
||||||
|
summary_template='%count% for %source.ip% to %destination.port%')
|
||||||
|
match = {'value_avg': 2.5, 'window_start': '2026-09-30T16:49:52+00:00', '@timestamp': '2026-09-30T16:50:00+00:00',
|
||||||
|
'source': {'ip': '192.0.2.10'}, 'destination': {'port': [22, 80, 443, 8080, 8443]}}
|
||||||
|
|
||||||
|
payload, _ = self.send(rule, match)
|
||||||
|
|
||||||
|
self.assertEqual(payload['event']['reason'], '2.50 for 192.0.2.10 to 22, 80, 443 and 2 more')
|
||||||
|
self.assertEqual(es.SecurityOnionESAlerter.format_count('n/a'), 'n/a')
|
||||||
|
|
||||||
|
def test_get_info(self):
|
||||||
|
self.assertEqual(es.SecurityOnionESAlerter(PLAIN_RULE).get_info(), {'type': 'SecurityOnionESAlerter'})
|
||||||
@@ -120,7 +120,7 @@ elastalert:
|
|||||||
helpLink: elastalert
|
helpLink: elastalert
|
||||||
old_query_limit:
|
old_query_limit:
|
||||||
minutes:
|
minutes:
|
||||||
description: Amount of time in minutes between queries to start at the most recently run query.
|
description: How long ElastAlert can be down, in minutes, and still resume each rule where it stopped. After a longer outage, rules restart from now and skip the gap.
|
||||||
global: True
|
global: True
|
||||||
helpLink: elastalert
|
helpLink: elastalert
|
||||||
es_conn_timeout:
|
es_conn_timeout:
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ 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 %}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ 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:
|
||||||
@@ -39,8 +40,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: 949
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
- template: jinja
|
- template: jinja
|
||||||
|
|
||||||
|
|||||||
@@ -26,13 +26,14 @@ 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: 947
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
- show_changes: False
|
- show_changes: False
|
||||||
|
|
||||||
@@ -40,8 +41,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: 947
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
- template: jinja
|
- template: jinja
|
||||||
- exclude_pat:
|
- exclude_pat:
|
||||||
@@ -81,8 +82,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: 947
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- mode: 755
|
- mode: 755
|
||||||
- template: jinja
|
- template: jinja
|
||||||
|
|
||||||
|
|||||||
@@ -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- add_fields:\n target: event\n fields:\n dataset: windows.forwarded\n module: windows\n imported: true\n- add_fields:\n target: \"@metadata\"\n fields:\n pipeline: import.evtx\n- if:\n equals:\n winlog.channel: 'Security'\n then: \n - add_fields:\n target: event\n fields:\n dataset: system.security\n module: system\n- if:\n equals:\n winlog.channel: 'Windows PowerShell'\n then: \n - add_fields:\n target: event\n fields:\n dataset: windows.powershell\n module: windows\n- if:\n equals:\n winlog.channel: 'Microsoft-Windows-Sysmon/Operational'\n then: \n - add_fields:\n target: event\n fields:\n dataset: windows.sysmon_operational\n module: windows\n imported: true\n- if:\n equals:\n winlog.channel: 'Application'\n then: \n - add_fields:\n target: event\n fields:\n dataset: system.application\n module: system\n- if:\n equals:\n winlog.channel: 'System'\n then: \n - add_fields:\n target: event\n fields:\n dataset: system.system\n module: system\n \n- if:\n equals:\n winlog.channel: 'Microsoft-Windows-PowerShell/Operational'\n then: \n - add_fields:\n target: event\n fields:\n dataset: windows.powershell_operational\n module: windows\n- add_fields:\n target: data_stream\n fields:\n type: logs\n dataset: import",
|
||||||
"tags": [
|
"tags": [
|
||||||
"import"
|
"import"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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: 947
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- mode: 755
|
- mode: 755
|
||||||
- show_changes: False
|
- show_changes: False
|
||||||
|
|
||||||
|
|||||||
@@ -30,17 +30,14 @@
|
|||||||
'azure_metrics.monitor': 'azure.monitor',
|
'azure_metrics.monitor': 'azure.monitor',
|
||||||
'azure_metrics.storage_account': 'azure.storage_account',
|
'azure_metrics.storage_account': 'azure.storage_account',
|
||||||
'azure_openai.metrics': 'azure.open_ai',
|
'azure_openai.metrics': 'azure.open_ai',
|
||||||
'beat.state': 'beats.stack_monitoring.state',
|
|
||||||
'beat.stats': 'beats.stack_monitoring.stats',
|
|
||||||
'enterprisesearch.health': 'enterprisesearch.stack_monitoring.health',
|
|
||||||
'enterprisesearch.stats': 'enterprisesearch.stack_monitoring.stats',
|
|
||||||
'kibana.cluster_actions': 'kibana.stack_monitoring.cluster_actions',
|
'kibana.cluster_actions': 'kibana.stack_monitoring.cluster_actions',
|
||||||
'kibana.cluster_rules': 'kibana.stack_monitoring.cluster_rules',
|
'kibana.cluster_rules': 'kibana.stack_monitoring.cluster_rules',
|
||||||
'kibana.node_actions': 'kibana.stack_monitoring.node_actions',
|
'kibana.node_actions': 'kibana.stack_monitoring.node_actions',
|
||||||
'kibana.node_rules': 'kibana.stack_monitoring.node_rules',
|
'kibana.node_rules': 'kibana.stack_monitoring.node_rules',
|
||||||
'kibana.stats': 'kibana.stack_monitoring.stats',
|
'kibana.stats': 'kibana.stack_monitoring.stats',
|
||||||
'kibana.status': 'kibana.stack_monitoring.status',
|
'kibana.status': 'kibana.stack_monitoring.status',
|
||||||
'logstash.node_cel': 'logstash.stack_monitoring.node',
|
'logstash.node': 'logstash.stack_monitoring.node',
|
||||||
|
'logstash.node_cel': 'logstash.node',
|
||||||
'logstash.node_stats': 'logstash.stack_monitoring.node_stats',
|
'logstash.node_stats': 'logstash.stack_monitoring.node_stats',
|
||||||
'synthetics.browser': 'synthetics-browser',
|
'synthetics.browser': 'synthetics-browser',
|
||||||
'synthetics.browser_network': 'synthetics-browser.network',
|
'synthetics.browser_network': 'synthetics-browser.network',
|
||||||
|
|||||||
@@ -30,6 +30,56 @@ 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}
|
||||||
|
|
||||||
@@ -62,15 +112,7 @@ 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=$(fleet_api "agent_policies/$AGENT_POLICY"); then
|
if ! POLICY_JSON=$(elastic_fleet_require_agent_policy "$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
|
||||||
@@ -124,9 +166,15 @@ elastic_fleet_integration_check() {
|
|||||||
|
|
||||||
JSON_STRING=$2
|
JSON_STRING=$2
|
||||||
|
|
||||||
NAME=$(jq -r .name $JSON_STRING)
|
NAME=$(jq -r .name "$JSON_STRING")
|
||||||
|
INTEGRATION_ID=""
|
||||||
|
|
||||||
INTEGRATION_ID=$(/usr/sbin/so-elastic-fleet-agent-policy-view "$AGENT_POLICY" | jq -r '.item.package_policies[] | select(.name=="'"$NAME"'") | .id')
|
local POLICY_JSON
|
||||||
|
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")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +196,16 @@ elastic_fleet_integration_remove() {
|
|||||||
|
|
||||||
NAME=$2
|
NAME=$2
|
||||||
|
|
||||||
INTEGRATION_ID=$(/usr/sbin/so-elastic-fleet-agent-policy-view "$AGENT_POLICY" | jq -r '.item.package_policies[] | select(.name=="'"$NAME"'") | .id')
|
local POLICY_JSON
|
||||||
|
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,7 +13,10 @@ 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"
|
||||||
elastic_fleet_integration_check "endpoints-initial" "$INTEGRATION"
|
if ! elastic_fleet_integration_check "endpoints-initial" "$INTEGRATION"; then
|
||||||
|
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
|
||||||
|
|||||||
+20
-5
@@ -7,20 +7,35 @@
|
|||||||
. /usr/sbin/so-elastic-fleet-common
|
. /usr/sbin/so-elastic-fleet-common
|
||||||
|
|
||||||
# Get all the fleet policies
|
# Get all the fleet policies
|
||||||
json_output=$(curl -s -K /opt/so/conf/elasticsearch/curl.config -L -X GET "localhost:5601/api/fleet/agent_policies" -H 'kbn-xsrf: true')
|
if ! json_output=$(fleet_api "agent_policies" -H 'kbn-xsrf: true'); then
|
||||||
|
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=$(echo "$json_output" | jq -r '.items[] | select(.id | startswith("FleetServer_")) | .id')
|
POLICY=$(jq -r '.items[] | select(.id | startswith("FleetServer_")) | .id' <<<"$json_output")
|
||||||
|
|
||||||
# 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"
|
||||||
|
|
||||||
# First get the Integration ID
|
if ! POLICY_JSON=$(elastic_fleet_require_agent_policy "$POLICYNAME"); then
|
||||||
INTEGRATION_ID=$(/usr/sbin/so-elastic-fleet-agent-policy-view "$POLICYNAME" | jq -r '.item.package_policies[] | select(.package.name == "fleet_server") | .id')
|
exit 1
|
||||||
|
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,12 +22,19 @@ 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=$(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')
|
ENROLLMENTOKEN=$(elastic_fleet_active_enrollment_token "endpoints-initial")
|
||||||
|
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 [[ $FLEETHOST ]] && [[ $ENROLLMENTOKEN ]]; then break; else sleep 10; fi
|
if [[ -n "$FLEETHOST" ]] && [[ -n "$ENROLLMENTOKEN" ]]; then
|
||||||
|
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
|
||||||
@@ -67,19 +74,25 @@ 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"
|
||||||
docker run -e CGO_ENABLED=0 -e GOOS=$GOOS -e GOARCH=$GOARCH \
|
if ! 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}
|
{{ 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
|
||||||
|
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
|
||||||
docker run \
|
if ! 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
|
{{ 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
|
||||||
|
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
|
||||||
|
|||||||
@@ -202,26 +202,9 @@ fi
|
|||||||
### Finalization ###
|
### Finalization ###
|
||||||
|
|
||||||
# Query for Enrollment Tokens for default policies
|
# Query for Enrollment Tokens for default policies
|
||||||
if ENDPOINTSENROLLMENTOKEN_RAW=$(fleet_api "enrollment_api_keys" -H 'kbn-xsrf: true' -H 'Content-Type: application/json'); then
|
ENDPOINTSENROLLMENTOKEN=$(elastic_fleet_active_enrollment_token "endpoints-initial") || exit 1
|
||||||
ENDPOINTSENROLLMENTOKEN=$(echo "$ENDPOINTSENROLLMENTOKEN_RAW" | jq .list | jq -r -c '.[] | select(.policy_id | contains("endpoints-initial")) | .api_key')
|
GRIDNODESENROLLMENTOKENGENERAL=$(elastic_fleet_active_enrollment_token "so-grid-nodes_general") || exit 1
|
||||||
else
|
GRIDNODESENROLLMENTOKENHEAVY=$(elastic_fleet_active_enrollment_token "so-grid-nodes_heavy") || exit 1
|
||||||
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
|
||||||
|
|||||||
@@ -32,13 +32,14 @@ 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: 930
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- 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
|
||||||
@@ -49,8 +50,8 @@ so-elasticsearch-system-indices-patch-script:
|
|||||||
file.managed:
|
file.managed:
|
||||||
- name: /usr/sbin/so-elasticsearch-system-indices-patch
|
- name: /usr/sbin/so-elasticsearch-system-indices-patch
|
||||||
- source: salt://elasticsearch/tools/sbin/so-elasticsearch-system-indices-patch
|
- source: salt://elasticsearch/tools/sbin/so-elasticsearch-system-indices-patch
|
||||||
- user: 930
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- mode: 755
|
- mode: 755
|
||||||
- show_changes: False
|
- show_changes: False
|
||||||
|
|
||||||
@@ -58,8 +59,8 @@ 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: 939
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
- template: jinja
|
- template: jinja
|
||||||
- exclude_pat:
|
- exclude_pat:
|
||||||
@@ -72,8 +73,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: 930
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- mode: 754
|
- mode: 754
|
||||||
- template: jinja
|
- template: jinja
|
||||||
- defaults:
|
- defaults:
|
||||||
@@ -84,8 +85,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: 930
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- mode: 754
|
- mode: 754
|
||||||
- show_changes: False
|
- show_changes: False
|
||||||
|
|
||||||
|
|||||||
@@ -1160,6 +1160,7 @@ elasticsearch:
|
|||||||
- so-fleet_agent_id_verification-1
|
- so-fleet_agent_id_verification-1
|
||||||
- so-logs-mappings
|
- so-logs-mappings
|
||||||
- so-logs-settings
|
- so-logs-settings
|
||||||
|
- detections-alerts-mappings
|
||||||
data_stream:
|
data_stream:
|
||||||
allow_custom_routing: false
|
allow_custom_routing: false
|
||||||
hidden: false
|
hidden: false
|
||||||
@@ -3309,6 +3310,7 @@ elasticsearch:
|
|||||||
composed_of:
|
composed_of:
|
||||||
- event-mappings
|
- event-mappings
|
||||||
- logs-system.security@package
|
- logs-system.security@package
|
||||||
|
- so-fleet_system.security_caseless-1
|
||||||
- logs-system.security@custom
|
- logs-system.security@custom
|
||||||
- so-fleet_integrations.ip_mappings-1
|
- so-fleet_integrations.ip_mappings-1
|
||||||
- so-fleet_globals-1
|
- so-fleet_globals-1
|
||||||
@@ -4175,6 +4177,7 @@ elasticsearch:
|
|||||||
index_template:
|
index_template:
|
||||||
composed_of:
|
composed_of:
|
||||||
- logs-windows.forwarded@package
|
- logs-windows.forwarded@package
|
||||||
|
- so-fleet_process_caseless-1
|
||||||
- logs-windows.forwarded@custom
|
- logs-windows.forwarded@custom
|
||||||
- so-fleet_integrations.ip_mappings-1
|
- so-fleet_integrations.ip_mappings-1
|
||||||
- so-fleet_globals-1
|
- so-fleet_globals-1
|
||||||
@@ -4224,6 +4227,7 @@ elasticsearch:
|
|||||||
index_template:
|
index_template:
|
||||||
composed_of:
|
composed_of:
|
||||||
- logs-windows.powershell@package
|
- logs-windows.powershell@package
|
||||||
|
- so-fleet_process_caseless-1
|
||||||
- logs-windows.powershell@custom
|
- logs-windows.powershell@custom
|
||||||
- so-fleet_integrations.ip_mappings-1
|
- so-fleet_integrations.ip_mappings-1
|
||||||
- so-fleet_globals-1
|
- so-fleet_globals-1
|
||||||
@@ -4273,6 +4277,7 @@ elasticsearch:
|
|||||||
index_template:
|
index_template:
|
||||||
composed_of:
|
composed_of:
|
||||||
- logs-windows.powershell_operational@package
|
- logs-windows.powershell_operational@package
|
||||||
|
- so-fleet_process_caseless-1
|
||||||
- logs-windows.powershell_operational@custom
|
- logs-windows.powershell_operational@custom
|
||||||
- so-fleet_integrations.ip_mappings-1
|
- so-fleet_integrations.ip_mappings-1
|
||||||
- so-fleet_globals-1
|
- so-fleet_globals-1
|
||||||
@@ -4322,6 +4327,7 @@ elasticsearch:
|
|||||||
index_template:
|
index_template:
|
||||||
composed_of:
|
composed_of:
|
||||||
- logs-windows.sysmon_operational@package
|
- logs-windows.sysmon_operational@package
|
||||||
|
- so-fleet_process_caseless-1
|
||||||
- logs-windows.sysmon_operational@custom
|
- logs-windows.sysmon_operational@custom
|
||||||
- so-fleet_integrations.ip_mappings-1
|
- so-fleet_integrations.ip_mappings-1
|
||||||
- so-fleet_globals-1
|
- so-fleet_globals-1
|
||||||
|
|||||||
@@ -99,7 +99,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"set": {
|
"set": {
|
||||||
"if": "ctx.tags != null && ctx.tags.contains('import')",
|
"if": "ctx.tags != null && ctx.tags.contains('import') && ctx._index != null && ctx._index.startsWith('logs-import-')",
|
||||||
"override": true,
|
"override": true,
|
||||||
"field": "data_stream.dataset",
|
"field": "data_stream.dataset",
|
||||||
"value": "import"
|
"value": "import"
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"set": {
|
"set": {
|
||||||
"if": "ctx.tags != null && ctx.tags.contains('import')",
|
"if": "ctx.tags != null && ctx.tags.contains('import') && ctx._index != null && ctx._index.startsWith('logs-import-')",
|
||||||
"override": true,
|
"override": true,
|
||||||
"field": "data_stream.namespace",
|
"field": "data_stream.namespace",
|
||||||
"value": "so"
|
"value": "so"
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"description" : "import.evtx: normalize imported EVTX and reroute to logs-<dataset>-import",
|
||||||
|
"processors" : [
|
||||||
|
{ "script": {
|
||||||
|
"description": "Host from the event, not the importing node",
|
||||||
|
"lang": "painless",
|
||||||
|
"source": "Map host = ['os': ['type': 'windows', 'family': 'windows', 'platform': 'windows']]; def cn = ctx.winlog?.computer_name; if (cn != null && cn.toString().length() > 0) { String name = cn.toString(); int dot = name.indexOf('.'); if (dot > 0) { name = name.substring(0, dot); } host.put('hostname', name); host.put('name', name.toLowerCase()); } ctx.host = host;"
|
||||||
|
} },
|
||||||
|
{ "script": {
|
||||||
|
"description": "String event IDs, as Winlogbeat sends",
|
||||||
|
"lang": "painless",
|
||||||
|
"source": "if (ctx.winlog?.event_id != null) { ctx.winlog.event_id = ctx.winlog.event_id.toString(); } if (ctx.event?.code != null) { ctx.event.code = ctx.event.code.toString(); }"
|
||||||
|
} },
|
||||||
|
{ "script": {
|
||||||
|
"description": "Unnamed <Data> to param1..N, as Winlogbeat",
|
||||||
|
"lang": "painless",
|
||||||
|
"if": "ctx.winlog?.event_data?.Data instanceof Map && ctx.winlog.event_data.Data['#text'] != null",
|
||||||
|
"source": "def t = ctx.winlog.event_data.Data['#text']; List vals = t instanceof List ? t : [t]; for (int i = 0; i < vals.size(); i++) { ctx.winlog.event_data['param' + (i + 1)] = vals.get(i); } ctx.winlog.event_data.remove('Data');"
|
||||||
|
} },
|
||||||
|
{ "script": {
|
||||||
|
"description": "String values and LF line endings, as Winlogbeat",
|
||||||
|
"lang": "painless",
|
||||||
|
"if": "ctx.winlog?.event_data instanceof Map || ctx.winlog?.user_data instanceof Map",
|
||||||
|
"source": "String lf = String.valueOf((char) 10); String crlf = String.valueOf((char) 13) + lf; for (def key : ['event_data', 'user_data']) { def m = ctx.winlog[key]; if (!(m instanceof Map)) { continue; } for (def e : m.entrySet()) { def v = e.getValue(); if (v instanceof String) { e.setValue(v.replace(crlf, lf)); } else if (v instanceof Number || v instanceof Boolean) { e.setValue(v.toString()); } } }"
|
||||||
|
} },
|
||||||
|
{ "set": { "description": "event.kind, as Winlogbeat", "field": "event.kind", "value": "event", "override": false } },
|
||||||
|
{ "set": { "field": "data_stream.dataset", "copy_from": "event.dataset", "override": true, "ignore_empty_value": true } },
|
||||||
|
{ "set": { "field": "data_stream.namespace", "value": "import", "override": true } },
|
||||||
|
{ "reroute": { "dataset": "{{data_stream.dataset}}", "namespace": "{{data_stream.namespace}}" } }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"_meta": {
|
||||||
|
"managed_by": "securityonion",
|
||||||
|
"managed": true
|
||||||
|
},
|
||||||
|
"description": "Custom pipeline for the System integration's auth data stream.",
|
||||||
|
"processors": [
|
||||||
|
{
|
||||||
|
"trim": {
|
||||||
|
"description": "Grok leaves a leading space on 'invalid user' names (elastic/integrations#12174) and, before 2.23.2, sudo padding",
|
||||||
|
"field": "user.name",
|
||||||
|
"ignore_missing": true,
|
||||||
|
"ignore_failure": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"trim": {
|
||||||
|
"description": "Appended from the untrimmed user.name",
|
||||||
|
"field": "related.user",
|
||||||
|
"ignore_missing": true,
|
||||||
|
"ignore_failure": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"script": {
|
||||||
|
"description": "Dedupe after trimming",
|
||||||
|
"if": "ctx.related?.user instanceof List",
|
||||||
|
"source": "ctx.related.user = new ArrayList(new LinkedHashSet(ctx.related.user));",
|
||||||
|
"ignore_failure": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -5,7 +5,8 @@
|
|||||||
{ "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": "file.bytes.missing", "ignore_missing": true } },
|
{ "rename": { "field": "message2.fileinfo.gaps", "target_field": "suricata.fileinfo.gaps", "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 } },
|
||||||
|
|||||||
+123
@@ -0,0 +1,123 @@
|
|||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"managed_by": "security_onion",
|
||||||
|
"managed": true,
|
||||||
|
"description": "Adds .caseless for Lucene queries. Restates each field's package type and .text."
|
||||||
|
},
|
||||||
|
"template": {
|
||||||
|
"mappings": {
|
||||||
|
"properties": {
|
||||||
|
"process": {
|
||||||
|
"properties": {
|
||||||
|
"executable": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"fields": {
|
||||||
|
"caseless": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"normalizer": "lowercase"
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"type": "match_only_text"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"fields": {
|
||||||
|
"caseless": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"normalizer": "lowercase"
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"type": "match_only_text"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"command_line": {
|
||||||
|
"type": "wildcard",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"fields": {
|
||||||
|
"caseless": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"normalizer": "lowercase"
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"type": "match_only_text"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parent": {
|
||||||
|
"properties": {
|
||||||
|
"executable": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"fields": {
|
||||||
|
"caseless": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"normalizer": "lowercase"
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"type": "match_only_text"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"fields": {
|
||||||
|
"caseless": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"normalizer": "lowercase"
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"type": "match_only_text"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"command_line": {
|
||||||
|
"type": "wildcard",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"fields": {
|
||||||
|
"caseless": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"normalizer": "lowercase"
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"type": "match_only_text"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"file": {
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"fields": {
|
||||||
|
"caseless": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"normalizer": "lowercase"
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"type": "match_only_text"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+80
@@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"managed_by": "security_onion",
|
||||||
|
"managed": true,
|
||||||
|
"description": "Adds .caseless for Lucene queries. Keeps each field's existing keyword type."
|
||||||
|
},
|
||||||
|
"template": {
|
||||||
|
"mappings": {
|
||||||
|
"properties": {
|
||||||
|
"process": {
|
||||||
|
"properties": {
|
||||||
|
"command_line": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"fields": {
|
||||||
|
"caseless": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"normalizer": "lowercase"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parent": {
|
||||||
|
"properties": {
|
||||||
|
"executable": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"fields": {
|
||||||
|
"caseless": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"normalizer": "lowercase"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"fields": {
|
||||||
|
"caseless": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"normalizer": "lowercase"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"command_line": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"fields": {
|
||||||
|
"caseless": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"normalizer": "lowercase"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"file": {
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"fields": {
|
||||||
|
"caseless": {
|
||||||
|
"type": "keyword",
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"normalizer": "lowercase"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,6 +50,18 @@
|
|||||||
"ignore_above": 1024,
|
"ignore_above": 1024,
|
||||||
"type": "keyword"
|
"type": "keyword"
|
||||||
},
|
},
|
||||||
|
"ruleType": {
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"correlationType": {
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"correlationTimespan": {
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
"content": {
|
"content": {
|
||||||
"type": "text"
|
"type": "text"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
{
|
||||||
|
"template": {
|
||||||
|
"mappings": {
|
||||||
|
"properties": {
|
||||||
|
"tags": {
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"sigma_level": {
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"rule": {
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"uuid": {
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"category": {
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"product": {
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"service": {
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"correlation": {
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"type": "keyword"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"event": {
|
||||||
|
"properties": {
|
||||||
|
"severity": {
|
||||||
|
"type": "long"
|
||||||
|
},
|
||||||
|
"severity_label": {
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"module": {
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"dataset": {
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"kind": {
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"reason": {
|
||||||
|
"type": "match_only_text",
|
||||||
|
"fields": {
|
||||||
|
"keyword": {
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"type": "keyword"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "keyword",
|
||||||
|
"index": false,
|
||||||
|
"doc_values": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"event_data": {
|
||||||
|
"properties": {
|
||||||
|
"@timestamp": {
|
||||||
|
"type": "date"
|
||||||
|
},
|
||||||
|
"window_start": {
|
||||||
|
"type": "date"
|
||||||
|
},
|
||||||
|
"event_count": {
|
||||||
|
"type": "long"
|
||||||
|
},
|
||||||
|
"value_count": {
|
||||||
|
"type": "long"
|
||||||
|
},
|
||||||
|
"event_type_count": {
|
||||||
|
"type": "long"
|
||||||
|
},
|
||||||
|
"value_sum": {
|
||||||
|
"type": "double"
|
||||||
|
},
|
||||||
|
"value_avg": {
|
||||||
|
"type": "double"
|
||||||
|
},
|
||||||
|
"value_percentile": {
|
||||||
|
"type": "double"
|
||||||
|
},
|
||||||
|
"value_median": {
|
||||||
|
"type": "double"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"properties": {
|
||||||
|
"correlation_group_by": {
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"correlation_group": {
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"type": "keyword"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"related": {
|
||||||
|
"properties": {
|
||||||
|
"ip": {
|
||||||
|
"type": "ip"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"hosts": {
|
||||||
|
"ignore_above": 1024,
|
||||||
|
"type": "keyword"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "match_only_text"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"_meta": {
|
||||||
|
"description": "Fields written by the ElastAlert SecurityOnionESAlerter to logs-detections.alerts-so"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,11 +4,19 @@
|
|||||||
{%- 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 #}
|
||||||
@@ -35,11 +43,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 sobridge -p " ~ proto ~ " -m " ~ proto ~ " --dport " ~ hostPort ~ " -j DNAT --to-destination " ~ DOCKERMERGED.containers[container].ip ~ ":" ~ containerPort) %}
|
{%- 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) %}
|
||||||
{%- else %}
|
{%- else %}
|
||||||
{%- do D1.append("-A DOCKER ! -i sobridge -p " ~ proto ~ " -m " ~ proto ~ " --dport " ~ hostPort ~ " -j DNAT --to-destination " ~ DOCKERMERGED.containers[container].ip ~ ":" ~ containerPort) %}
|
{%- do D1.append("-A DOCKER ! -i " ~ BRIDGE ~ " -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 sobridge -o sobridge -p " ~ proto ~ " -m " ~ proto ~ " --dport " ~ containerPort ~ " -j ACCEPT") %}
|
{%- do D2.append("-A DOCKER -d " ~ DOCKERMERGED.containers[container].ip ~ "/32 ! -i " ~ BRIDGE ~ " -o " ~ BRIDGE ~ " -p " ~ proto ~ " -m " ~ proto ~ " --dport " ~ containerPort ~ " -j ACCEPT") %}
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
@@ -52,11 +60,15 @@
|
|||||||
: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
|
||||||
-A POSTROUTING -s {{DOCKERMERGED.range}} ! -o sobridge -j MASQUERADE
|
{%- for NETNAME in NODE_NETWORKS %}
|
||||||
|
-A POSTROUTING -s {{ DOCKERMERGED.networks[NETNAME].range }} ! -o {{ NETNAME }} -j MASQUERADE
|
||||||
|
{%- endfor %}
|
||||||
{%- for rule in PR %}
|
{%- for rule in PR %}
|
||||||
{{ rule }}
|
{{ rule }}
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
-A DOCKER -i sobridge -j RETURN
|
{%- for NETNAME in NODE_NETWORKS %}
|
||||||
|
-A DOCKER -i {{ NETNAME }} -j RETURN
|
||||||
|
{%- endfor %}
|
||||||
{%- for rule in D1 %}
|
{%- for rule in D1 %}
|
||||||
{{ rule }}
|
{{ rule }}
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
@@ -97,10 +109,12 @@ 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
|
||||||
-A FORWARD -o sobridge -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
{%- for NETNAME in NODE_NETWORKS %}
|
||||||
-A FORWARD -o sobridge -j DOCKER
|
-A FORWARD -o {{ NETNAME }} -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
-A FORWARD -i sobridge ! -o sobridge -j ACCEPT
|
-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 {{ 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
|
||||||
@@ -112,13 +126,18 @@ COMMIT
|
|||||||
{%- for rule in D2 %}
|
{%- for rule in D2 %}
|
||||||
{{ rule }}
|
{{ rule }}
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
|
{% for NETNAME in NODE_NETWORKS %}
|
||||||
-A DOCKER-ISOLATION-STAGE-1 -i sobridge ! -o sobridge -j DOCKER-ISOLATION-STAGE-2
|
-A DOCKER-ISOLATION-STAGE-1 -i {{ NETNAME }} ! -o {{ NETNAME }} -j DOCKER-ISOLATION-STAGE-2
|
||||||
|
{%- endfor %}
|
||||||
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
|
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
|
||||||
-A DOCKER-ISOLATION-STAGE-2 -o sobridge -j DROP
|
{%- for NETNAME in NODE_NETWORKS %}
|
||||||
|
-A DOCKER-ISOLATION-STAGE-2 -o {{ NETNAME }} -j DROP
|
||||||
|
{%- endfor %}
|
||||||
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
|
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
|
||||||
-A DOCKER-USER ! -i sobridge -o sobridge -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
{%- for NETNAME in NODE_NETWORKS %}
|
||||||
-A DOCKER-USER ! -i sobridge -o sobridge -j LOGGING
|
-A DOCKER-USER ! -i {{ NETNAME }} -o {{ NETNAME }} -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
-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
|
||||||
|
|||||||
@@ -4,8 +4,12 @@
|
|||||||
|
|
||||||
{# 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 range #}
|
{# add dockernet ranges #}
|
||||||
{% do FIREWALL_DEFAULT.firewall.hostgroups.dockernet.append(DOCKERMERGED.range) %}
|
{% for NETNAME, NETWORK in DOCKERMERGED.networks.items() %}
|
||||||
|
{% 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 %}
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ so-hydra:
|
|||||||
- hostname: hydra
|
- hostname: hydra
|
||||||
- name: so-hydra
|
- name: so-hydra
|
||||||
- networks:
|
- networks:
|
||||||
- sobridge:
|
- soauth:
|
||||||
- ipv4_address: {{ DOCKERMERGED.containers['so-hydra'].ip }}
|
- ipv4_address: {{ DOCKERMERGED.containers['so-hydra'].ips['soauth'] }}
|
||||||
- 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://{{ GLOBALS.manager }}:4444/health/alive'
|
- name: 'http://{{ DOCKERMERGED.containers['so-hydra'].ips['soauth'] }}:4444/health/alive'
|
||||||
- ssl: True
|
- ssl: True
|
||||||
- verify_ssl: False
|
- verify_ssl: False
|
||||||
- status:
|
- status:
|
||||||
|
|||||||
@@ -21,12 +21,16 @@ 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
@@ -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: 939
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
|
|
||||||
#idh_sbin_jinja:
|
#idh_sbin_jinja:
|
||||||
|
|||||||
@@ -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: 939
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
|
|
||||||
#influxdb_sbin_jinja:
|
#influxdb_sbin_jinja:
|
||||||
|
|||||||
@@ -94,9 +94,11 @@ 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 2>&1'
|
- 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'
|
||||||
- identifier: get_influxdb_size
|
- identifier: get_influxdb_size
|
||||||
- user: root
|
- user: root
|
||||||
- minute: '*/1'
|
- minute: '*/1'
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ 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:
|
||||||
@@ -30,16 +31,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: 960
|
- user: root
|
||||||
- group: 960
|
- group: root
|
||||||
- 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: 960
|
- user: root
|
||||||
- group: 960
|
- group: root
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
- template: jinja
|
- template: jinja
|
||||||
- defaults:
|
- defaults:
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ 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
|
||||||
|
|
||||||
@@ -36,16 +37,16 @@ kibana_sbin:
|
|||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://kibana/tools/sbin
|
- source: salt://kibana/tools/sbin
|
||||||
- user: 932
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- 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: 932
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
- template: jinja
|
- template: jinja
|
||||||
- defaults:
|
- defaults:
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ 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:
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ so-kratos:
|
|||||||
- hostname: kratos
|
- hostname: kratos
|
||||||
- name: so-kratos
|
- name: so-kratos
|
||||||
- networks:
|
- networks:
|
||||||
- sobridge:
|
- soauth:
|
||||||
- ipv4_address: {{ DOCKERMERGED.containers['so-kratos'].ip }}
|
- ipv4_address: {{ DOCKERMERGED.containers['so-kratos'].ips['soauth'] }}
|
||||||
- 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://{{ GLOBALS.manager }}:4434/'
|
- name: 'http://{{ DOCKERMERGED.containers['so-kratos'].ips['soauth'] }}:4434/'
|
||||||
- ssl: True
|
- ssl: True
|
||||||
- verify_ssl: False
|
- verify_ssl: False
|
||||||
- status:
|
- status:
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ 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:
|
||||||
|
|||||||
@@ -35,13 +35,14 @@ 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: 931
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
|
|
||||||
#logstash_sbin_jinja:
|
#logstash_sbin_jinja:
|
||||||
|
|||||||
+12
-8
@@ -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: 939
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- 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: socore
|
- user: root
|
||||||
- group: socore
|
- group: root
|
||||||
- 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 2>&1
|
- 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'
|
||||||
- identifier: so_fleetagent_status
|
- identifier: so_fleetagent_status
|
||||||
- user: root
|
- user: root
|
||||||
- minute: '*/5'
|
- minute: '*/5'
|
||||||
@@ -190,11 +190,15 @@ so_fleetagent_monitor:
|
|||||||
- month: '*'
|
- month: '*'
|
||||||
- dayweek: '*'
|
- dayweek: '*'
|
||||||
|
|
||||||
socore_own_saltstack_default:
|
# This tree is the source of every root-executed script (/usr/sbin, reactors, _runners,
|
||||||
|
# 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: socore
|
- user: root
|
||||||
- group: socore
|
- group: root
|
||||||
- recurse:
|
- recurse:
|
||||||
- user
|
- user
|
||||||
- group
|
- group
|
||||||
|
|||||||
@@ -106,7 +106,8 @@ while [[ $# -gt 0 ]]; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
hydraUrl=${HYDRA_URL:-http://127.0.0.1:4445}
|
hydraContainer=${HYDRA_CONTAINER:-so-hydra}
|
||||||
|
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}
|
||||||
@@ -124,6 +125,10 @@ 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
|
||||||
@@ -133,8 +138,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 "curl"
|
require "docker"
|
||||||
response=$(curl -Ss -L ${hydraUrl}/health/alive)
|
response=$(hydraCurl -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"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +169,7 @@ function ensureRoleFileExists() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function listClients() {
|
function listClients() {
|
||||||
response=$(curl -Ss -L -f ${hydraUrl}/admin/clients)
|
response=$(hydraCurl -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)
|
||||||
@@ -251,7 +256,7 @@ function createClient() {
|
|||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
|
|
||||||
response=$(curl -Ss -L --fail-with-body -X POST ${hydraUrl}/admin/clients -d "$body")
|
response=$(hydraCurl -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"
|
||||||
@@ -283,7 +288,7 @@ function update() {
|
|||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
|
|
||||||
response=$(curl -Ss -L --fail-with-body -X PATCH ${hydraUrl}/admin/clients/$id -d "$body")
|
response=$(hydraCurl -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"
|
||||||
@@ -305,7 +310,7 @@ function generateSecret() {
|
|||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
|
|
||||||
response=$(curl -Ss -L --fail-with-body -X PATCH ${hydraUrl}/admin/clients/$id -d "$body")
|
response=$(hydraCurl -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"
|
||||||
@@ -317,7 +322,7 @@ function deleteClient() {
|
|||||||
|
|
||||||
[[ ${identityId} == "" ]] && fail "Client not found"
|
[[ ${identityId} == "" ]] && fail "Client not found"
|
||||||
|
|
||||||
response=$(curl -Ss -XDELETE -L --fail-with-body "${hydraUrl}/admin/clients/$identityId")
|
response=$(hydraCurl -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"
|
||||||
|
|||||||
@@ -121,8 +121,14 @@ for i in "$@"; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
PILLARFILE=/opt/so/saltstack/local/pillar/minions/$MINION_ID.sls
|
if [[ -n "$MINION_ID" && ! "$MINION_ID" =~ ^[A-Za-z0-9._-]{1,253}$ ]]; then
|
||||||
ADVPILLARFILE=/opt/so/saltstack/local/pillar/minions/adv_$MINION_ID.sls
|
echo "Invalid minion id: $MINION_ID"
|
||||||
|
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"
|
||||||
@@ -133,10 +139,23 @@ function getinstallinfo() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
while read -r var; do export "$var"; done <<< "$INSTALLVARS"
|
# install.txt is controlled by the minion; only accept known keys and never eval or export them
|
||||||
if [ $? -ne 0 ]; then
|
local line key
|
||||||
log "ERROR" "Failed to source install variables"
|
while IFS= read -r line; do
|
||||||
return 1
|
[[ "$line" == *=* ]] || continue
|
||||||
|
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})"
|
log "INFO" "Fetched install info for $MINION_ID (node type: ${NODETYPE:-unset})"
|
||||||
@@ -176,6 +195,12 @@ 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 ))
|
||||||
|
|
||||||
@@ -1050,6 +1075,57 @@ 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 (pillar: $PILLARFILE)"
|
||||||
|
|
||||||
@@ -1061,6 +1137,8 @@ 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
|
||||||
|
|
||||||
|
|||||||
@@ -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 socore:socore $default_salt_dir/salt
|
chown -R root:root $default_salt_dir/salt
|
||||||
chown -R socore:socore $default_salt_dir/pillar
|
chown -R root:root $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
|
||||||
|
|||||||
@@ -129,7 +129,8 @@ while [[ $# -gt 0 ]]; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
kratosUrl=${KRATOS_URL:-http://127.0.0.1:4434/admin}
|
kratosContainer=${KRATOS_CONTAINER:-so-kratos}
|
||||||
|
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}
|
||||||
@@ -154,6 +155,10 @@ 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
|
||||||
@@ -164,18 +169,18 @@ function require() {
|
|||||||
function verifyEnvironment() {
|
function verifyEnvironment() {
|
||||||
require "htpasswd"
|
require "htpasswd"
|
||||||
require "jq"
|
require "jq"
|
||||||
require "curl"
|
require "docker"
|
||||||
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=$(curl -Ss -L ${kratosUrl}/)
|
response=$(kratosCurl -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=$(curl -Ss -L ${kratosUrl}/identities)
|
response=$(kratosCurl -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
|
||||||
}
|
}
|
||||||
@@ -416,7 +421,7 @@ function syncAll() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function listUsers() {
|
function listUsers() {
|
||||||
response=$(curl -Ss -L ${kratosUrl}/identities)
|
response=$(kratosCurl -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)
|
||||||
@@ -495,7 +500,7 @@ function createUser() {
|
|||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
|
|
||||||
response=$(curl -Ss -L ${kratosUrl}/identities -d "$addUserJson")
|
response=$(kratosCurl -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")
|
||||||
@@ -518,7 +523,7 @@ function updateStatus() {
|
|||||||
identityId=$(findIdByEmail "$email")
|
identityId=$(findIdByEmail "$email")
|
||||||
[[ ${identityId} == "" ]] && fail "User not found"
|
[[ ${identityId} == "" ]] && fail "User not found"
|
||||||
|
|
||||||
response=$(curl -Ss -L "${kratosUrl}/identities/$identityId")
|
response=$(kratosCurl -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)
|
||||||
@@ -531,7 +536,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=$(curl -fSsL -XPUT -H "Content-Type: application/json" "${kratosUrl}/identities/$identityId" -d "$body")
|
response=$(kratosCurl -fSsL -XPUT -H "Content-Type: application/json" "${kratosUrl}/identities/$identityId" -d "$body")
|
||||||
[[ $? != 0 ]] && fail "Unable to update user"
|
[[ $? != 0 ]] && fail "Unable to update user"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,7 +555,7 @@ function updateUserProfile() {
|
|||||||
identityId=$(findIdByEmail "$email")
|
identityId=$(findIdByEmail "$email")
|
||||||
[[ ${identityId} == "" ]] && fail "User not found"
|
[[ ${identityId} == "" ]] && fail "User not found"
|
||||||
|
|
||||||
response=$(curl -Ss -L "${kratosUrl}/identities/$identityId")
|
response=$(kratosCurl -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)
|
||||||
@@ -559,7 +564,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=$(curl -fSsL -XPUT -H "Content-Type: application/json" "${kratosUrl}/identities/$identityId" -d "$body")
|
response=$(kratosCurl -fSsL -XPUT -H "Content-Type: application/json" "${kratosUrl}/identities/$identityId" -d "$body")
|
||||||
[[ $? != 0 ]] && fail "Unable to update user"
|
[[ $? != 0 ]] && fail "Unable to update user"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -569,7 +574,7 @@ function deleteUser() {
|
|||||||
identityId=$(findIdByEmail "$email")
|
identityId=$(findIdByEmail "$email")
|
||||||
[[ ${identityId} == "" ]] && fail "User not found"
|
[[ ${identityId} == "" ]] && fail "User not found"
|
||||||
|
|
||||||
response=$(curl -Ss -XDELETE -L "${kratosUrl}/identities/$identityId")
|
response=$(kratosCurl -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"
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ def loadYaml(filename):
|
|||||||
try:
|
try:
|
||||||
with open(filename, "r") as file:
|
with open(filename, "r") as file:
|
||||||
content = file.read()
|
content = file.read()
|
||||||
return yaml.safe_load(content)
|
loaded = yaml.safe_load(content)
|
||||||
|
return loaded if loaded is not None else {}
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print(f"File not found: {filename}", file=sys.stderr)
|
print(f"File not found: {filename}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|||||||
@@ -95,6 +95,20 @@ class TestRemove(unittest.TestCase):
|
|||||||
expected = "key1:\n child1: 123\n child2:\n deep2: ab\nkey2: false\n"
|
expected = "key1:\n child1: 123\n child2:\n deep2: ab\nkey2: false\n"
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
def test_remove_empty_file(self):
|
||||||
|
filename = "/tmp/so-yaml_test-remove-empty.yaml"
|
||||||
|
file = open(filename, "w")
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
code = soyaml.remove([filename, "key1"])
|
||||||
|
self.assertEqual(code, 0)
|
||||||
|
|
||||||
|
file = open(filename, "r")
|
||||||
|
actual = file.read()
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
self.assertEqual(actual, "{}\n")
|
||||||
|
|
||||||
def test_remove_missing_args(self):
|
def test_remove_missing_args(self):
|
||||||
with patch('sys.exit', new=MagicMock()) as sysmock:
|
with patch('sys.exit', new=MagicMock()) as sysmock:
|
||||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||||
@@ -294,6 +308,36 @@ class TestRemove(unittest.TestCase):
|
|||||||
expected = "key1:\n child1: 123\n child2:\n deep1: 45\n deep2: d\nkey2: false\nkey3:\n- e\n- f\n- g\n"
|
expected = "key1:\n child1: 123\n child2:\n deep1: 45\n deep2: d\nkey2: false\nkey3:\n- e\n- f\n- g\n"
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
def test_add_empty_file(self):
|
||||||
|
filename = "/tmp/so-yaml_test-add-empty.yaml"
|
||||||
|
file = open(filename, "w")
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
code = soyaml.add([filename, "telegraf.output", "BOTH"])
|
||||||
|
self.assertEqual(code, 0)
|
||||||
|
|
||||||
|
file = open(filename, "r")
|
||||||
|
actual = file.read()
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
expected = "telegraf:\n output: BOTH\n"
|
||||||
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
def test_add_empty_file_simple(self):
|
||||||
|
filename = "/tmp/so-yaml_test-add-empty-simple.yaml"
|
||||||
|
file = open(filename, "w")
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
code = soyaml.add([filename, "telegraf", "BOTH"])
|
||||||
|
self.assertEqual(code, 0)
|
||||||
|
|
||||||
|
file = open(filename, "r")
|
||||||
|
actual = file.read()
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
expected = "telegraf: BOTH\n"
|
||||||
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
def test_replace_missing_arg(self):
|
def test_replace_missing_arg(self):
|
||||||
with patch('sys.exit', new=MagicMock()) as sysmock:
|
with patch('sys.exit', new=MagicMock()) as sysmock:
|
||||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||||
@@ -346,6 +390,21 @@ class TestRemove(unittest.TestCase):
|
|||||||
expected = "key1:\n child1: 123\n child2:\n deep1: 46\nkey2: false\nkey3:\n- e\n- f\n- g\n"
|
expected = "key1:\n child1: 123\n child2:\n deep1: 46\nkey2: false\nkey3:\n- e\n- f\n- g\n"
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
def test_replace_empty_file(self):
|
||||||
|
filename = "/tmp/so-yaml_test-replace-empty.yaml"
|
||||||
|
file = open(filename, "w")
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
code = soyaml.replace([filename, "telegraf.output", "BOTH"])
|
||||||
|
self.assertEqual(code, 0)
|
||||||
|
|
||||||
|
file = open(filename, "r")
|
||||||
|
actual = file.read()
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
expected = "telegraf:\n output: BOTH\n"
|
||||||
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
def test_convert(self):
|
def test_convert(self):
|
||||||
self.assertEqual(soyaml.convertType("foo"), "foo")
|
self.assertEqual(soyaml.convertType("foo"), "foo")
|
||||||
self.assertEqual(soyaml.convertType("foo.bar"), "foo.bar")
|
self.assertEqual(soyaml.convertType("foo.bar"), "foo.bar")
|
||||||
@@ -506,6 +565,18 @@ class TestRemove(unittest.TestCase):
|
|||||||
self.assertEqual(result, 2)
|
self.assertEqual(result, 2)
|
||||||
self.assertEqual("", mock_stdout.getvalue())
|
self.assertEqual("", mock_stdout.getvalue())
|
||||||
|
|
||||||
|
def test_get_empty_file(self):
|
||||||
|
with patch('sys.stdout', new=StringIO()) as mock_stdout:
|
||||||
|
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||||
|
filename = "/tmp/so-yaml_test-get-empty.yaml"
|
||||||
|
file = open(filename, "w")
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
result = soyaml.get([filename, "telegraf.output"])
|
||||||
|
self.assertEqual(result, 2)
|
||||||
|
self.assertEqual("", mock_stdout.getvalue())
|
||||||
|
self.assertIn("Key 'telegraf.output' not found by so-yaml.py", mock_stderr.getvalue())
|
||||||
|
|
||||||
def test_get_usage(self):
|
def test_get_usage(self):
|
||||||
with patch('sys.exit', new=MagicMock()) as sysmock:
|
with patch('sys.exit', new=MagicMock()) as sysmock:
|
||||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||||
@@ -991,3 +1062,29 @@ class TestLoadYaml(unittest.TestCase):
|
|||||||
soyaml.loadYaml("/tmp/so-yaml_test-unreadable.yaml")
|
soyaml.loadYaml("/tmp/so-yaml_test-unreadable.yaml")
|
||||||
sysmock.assert_called_with(1)
|
sysmock.assert_called_with(1)
|
||||||
self.assertIn("Error reading file", mock_stderr.getvalue())
|
self.assertIn("Error reading file", mock_stderr.getvalue())
|
||||||
|
|
||||||
|
def test_load_yaml_empty_file(self):
|
||||||
|
filename = "/tmp/so-yaml_test-load-empty.yaml"
|
||||||
|
file = open(filename, "w")
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
result = soyaml.loadYaml(filename)
|
||||||
|
self.assertEqual(result, {})
|
||||||
|
|
||||||
|
def test_load_yaml_whitespace_only(self):
|
||||||
|
filename = "/tmp/so-yaml_test-load-whitespace.yaml"
|
||||||
|
file = open(filename, "w")
|
||||||
|
file.write(" \n\n \n")
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
result = soyaml.loadYaml(filename)
|
||||||
|
self.assertEqual(result, {})
|
||||||
|
|
||||||
|
def test_load_yaml_comments_only(self):
|
||||||
|
filename = "/tmp/so-yaml_test-load-comments.yaml"
|
||||||
|
file = open(filename, "w")
|
||||||
|
file.write("# Just a comment\n# Another comment\n")
|
||||||
|
file.close()
|
||||||
|
|
||||||
|
result = soyaml.loadYaml(filename)
|
||||||
|
self.assertEqual(result, {})
|
||||||
@@ -28,6 +28,7 @@ INSTALLEDSALTVERSION=$(salt --versions-report | grep Salt: | awk '{print $2}')
|
|||||||
# percentage like "25%"). Empty means so-soup-grid-highstate uses the salt:auto_apply:batch
|
# percentage like "25%"). Empty means so-soup-grid-highstate uses the salt:auto_apply:batch
|
||||||
# pillar default.
|
# pillar default.
|
||||||
BATCHSIZE=
|
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
|
||||||
@@ -120,6 +121,9 @@ 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."
|
||||||
@@ -347,6 +351,83 @@ check_cluster_health() {
|
|||||||
exit 0
|
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
|
||||||
|
no_soup_for_you
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
check_fleet_server() {
|
check_fleet_server() {
|
||||||
echo "Checking that Elastic Fleet Server is responding."
|
echo "Checking that Elastic Fleet Server is responding."
|
||||||
# Modeled on the wait_for_so-elastic-fleet state check in elasticfleet/enabled.sls,
|
# Modeled on the wait_for_so-elastic-fleet state check in elasticfleet/enabled.sls,
|
||||||
@@ -525,6 +606,7 @@ preupgrade_changes() {
|
|||||||
[[ "$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.2.0" ]] && up_to_3.3.0
|
||||||
|
[[ "$INSTALLEDVERSION" == "3.3.0" ]] && up_to_3.4.0
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,6 +625,7 @@ postupgrade_changes() {
|
|||||||
[[ "$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.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
|
||||||
@@ -1093,6 +1176,98 @@ post_to_3.3.0() {
|
|||||||
}
|
}
|
||||||
### 3.3.0 End ###
|
### 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
|
||||||
|
|
||||||
|
# backfill the Sigma rule type on existing detections
|
||||||
|
mkdir -p /opt/so/conf/soc/migrations
|
||||||
|
echo "0" > /opt/so/conf/soc/migrations/elastalert-migration-3.4.0
|
||||||
|
chown -R socore:socore /opt/so/conf/soc/migrations
|
||||||
|
|
||||||
|
for template in so-metrics-logstash.node so-metrics-logstash.stack_monitoring.node; do
|
||||||
|
if ! remove_elasticsearch_index_template "$template" "logstash node and node_cel index patterns reversed"; then
|
||||||
|
FINAL_MESSAGE_QUEUE+=("WARNING: Unable to automatically remove the $template index template. Addon integration templates may fail to load until it is removed:")
|
||||||
|
FINAL_MESSAGE_QUEUE+=(" - sudo so-elasticsearch-query _index_template/$template -XDELETE && so-checkin")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
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() {
|
||||||
|
for idx in "metrics-logstash.node-default" "metrics-logstash.stack_monitoring.node-default"; do
|
||||||
|
rollover_index "$idx"
|
||||||
|
done
|
||||||
|
|
||||||
|
set_postversion 3.4.0
|
||||||
|
}
|
||||||
|
### 3.4.0 End ###
|
||||||
|
|
||||||
|
|
||||||
repo_sync() {
|
repo_sync() {
|
||||||
echo "Sync the local repo."
|
echo "Sync the local repo."
|
||||||
@@ -1977,6 +2152,9 @@ 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."
|
||||||
@@ -2255,6 +2433,17 @@ 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 )
|
||||||
@@ -2278,7 +2467,7 @@ while getopts ":b:f:y" opt; do
|
|||||||
ISOLOC="$OPTARG"
|
ISOLOC="$OPTARG"
|
||||||
;;
|
;;
|
||||||
\? )
|
\? )
|
||||||
echo "Usage: soup [-b] [-y] [-f <iso location>]"
|
echo "Usage: soup [-b] [-y] [-f <iso location>] [--skip-cpu-check]"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
: )
|
: )
|
||||||
|
|||||||
@@ -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: 939
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
|
|
||||||
#nginx_sbin_jinja:
|
#nginx_sbin_jinja:
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ http {
|
|||||||
ssl_prefer_server_ciphers on;
|
ssl_prefer_server_ciphers on;
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
|
||||||
location ~* (^/login/.*|^/js/.*|^/css/.*|^/images/.*|^/pages/.*|^/docs/.*) {
|
location ~* (^/login|^/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;
|
||||||
|
|||||||
@@ -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: 939
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- 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: 939
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
- template: jinja
|
- template: jinja
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
'epel-testing.repo',
|
'epel-testing.repo',
|
||||||
'saltstack.repo',
|
'saltstack.repo',
|
||||||
'salt-latest.repo',
|
'salt-latest.repo',
|
||||||
'wazuh.repo'
|
'wazuh.repo',
|
||||||
'Rocky-Base.repo',
|
'Rocky-Base.repo',
|
||||||
'Rocky-CR.repo',
|
'Rocky-CR.repo',
|
||||||
'Rocky-Debuginfo.repo',
|
'Rocky-Debuginfo.repo',
|
||||||
|
|||||||
+4
-2
@@ -3,6 +3,8 @@ 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
|
||||||
|
|
||||||
@@ -10,6 +12,6 @@ salt_sbin:
|
|||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://salt/tools/sbin
|
- source: salt://salt/tools/sbin
|
||||||
- user: 939
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
@@ -35,6 +35,8 @@ combine_bond_script:
|
|||||||
file.managed:
|
file.managed:
|
||||||
- name: /usr/sbin/so-combine-bond
|
- name: /usr/sbin/so-combine-bond
|
||||||
- source: salt://sensor/tools/sbin_jinja/so-combine-bond
|
- source: salt://sensor/tools/sbin_jinja/so-combine-bond
|
||||||
|
- user: root
|
||||||
|
- group: root
|
||||||
- mode: 755
|
- mode: 755
|
||||||
- template: jinja
|
- template: jinja
|
||||||
- defaults:
|
- defaults:
|
||||||
|
|||||||
@@ -64,8 +64,8 @@ sensoroni_sbin:
|
|||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://sensoroni/tools/sbin
|
- source: salt://sensoroni/tools/sbin
|
||||||
- user: 939
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
|
|
||||||
#sensoroni_sbin_jinja:
|
#sensoroni_sbin_jinja:
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
requests>=2.31.0
|
requests>=2.34.0
|
||||||
whoisit>=2.7.0
|
whoisit>=4.0.5
|
||||||
|
anyio>=4.15.1
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+20
-8
@@ -118,21 +118,33 @@ crondetectionsbackup:
|
|||||||
- month: '*'
|
- month: '*'
|
||||||
- dayweek: '*'
|
- dayweek: '*'
|
||||||
|
|
||||||
|
# sigma-cli only loads *.yml from the pipelines dir
|
||||||
socsigmafinalpipeline:
|
socsigmafinalpipeline:
|
||||||
file.managed:
|
file.managed:
|
||||||
- name: /opt/so/conf/soc/sigma_final_pipeline.yaml
|
- name: /opt/so/conf/soc/sigma_pipelines/sigma_final_pipeline.yml
|
||||||
- source: salt://soc/files/soc/sigma_final_pipeline.yaml
|
- source: salt://soc/files/soc/sigma_final_pipeline.yaml
|
||||||
- user: 939
|
- user: 939
|
||||||
- group: 939
|
- group: 939
|
||||||
- mode: 600
|
- mode: 600
|
||||||
|
- makedirs: True
|
||||||
|
|
||||||
socsigmasopipeline:
|
# sigma-cli loads every *.yml here; clean removes anything else
|
||||||
file.managed:
|
socsigmapipelines:
|
||||||
- name: /opt/so/conf/soc/sigma_so_pipeline.yaml
|
file.recurse:
|
||||||
- source: salt://soc/files/soc/sigma_so_pipeline.yaml
|
- name: /opt/so/conf/soc/sigma_pipelines
|
||||||
|
- source: salt://soc/files/soc/sigma_pipelines
|
||||||
- user: 939
|
- user: 939
|
||||||
- group: 939
|
- group: 939
|
||||||
- mode: 600
|
- file_mode: 600
|
||||||
|
- clean: True
|
||||||
|
- require:
|
||||||
|
- file: socsigmafinalpipeline
|
||||||
|
|
||||||
|
socsigmapipelinesold:
|
||||||
|
file.absent:
|
||||||
|
- names:
|
||||||
|
- /opt/so/conf/soc/sigma_final_pipeline.yaml
|
||||||
|
- /opt/so/conf/soc/sigma_so_pipeline.yaml
|
||||||
|
|
||||||
socsigmaplaybookpipeline:
|
socsigmaplaybookpipeline:
|
||||||
file.managed:
|
file.managed:
|
||||||
@@ -171,8 +183,8 @@ soc_sbin:
|
|||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://soc/tools/sbin
|
- source: salt://soc/tools/sbin
|
||||||
- user: 939
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
|
|
||||||
#soc_sbin_jinja:
|
#soc_sbin_jinja:
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
{% do SOCDEFAULTS.soc.config.server.modules[module].update({'hostUrl': application_url}) %}
|
{% do SOCDEFAULTS.soc.config.server.modules[module].update({'hostUrl': application_url}) %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
{% do SOCDEFAULTS.soc.config.server.modules.kratos.update({'publicHostUrl': 'http://' ~ DOCKERMERGED.containers['so-kratos'].ips['soauth'] ~ ':4433/'}) %}
|
||||||
|
|
||||||
{# add all grid heavy nodes to soc.server.modules.elastic.remoteHostUrls #}
|
{# add all grid heavy nodes to soc.server.modules.elastic.remoteHostUrls #}
|
||||||
{% for node_type, minions in salt['pillar.get']('elasticsearch:nodes', {}).items() %}
|
{% for node_type, minions in salt['pillar.get']('elasticsearch:nodes', {}).items() %}
|
||||||
{% if node_type in ['heavynode'] %}
|
{% if node_type in ['heavynode'] %}
|
||||||
|
|||||||
+139
-6
@@ -1380,6 +1380,7 @@ soc:
|
|||||||
retryFailureMaxAttempts: 5
|
retryFailureMaxAttempts: 5
|
||||||
kratos:
|
kratos:
|
||||||
hostUrl:
|
hostUrl:
|
||||||
|
publicHostUrl:
|
||||||
hydra:
|
hydra:
|
||||||
hostUrl:
|
hostUrl:
|
||||||
elastalertengine:
|
elastalertengine:
|
||||||
@@ -1444,7 +1445,7 @@ soc:
|
|||||||
default:
|
default:
|
||||||
- repo: https://github.com/Security-Onion-Solutions/securityonion-resources
|
- repo: https://github.com/Security-Onion-Solutions/securityonion-resources
|
||||||
license: Elastic-2.0
|
license: Elastic-2.0
|
||||||
folder: sigma/stable
|
folder: sigma
|
||||||
community: true
|
community: true
|
||||||
rulesetName: securityonion-resources
|
rulesetName: securityonion-resources
|
||||||
- repo: file:///nsm/rules/custom-local-repos/local-sigma
|
- repo: file:///nsm/rules/custom-local-repos/local-sigma
|
||||||
@@ -1454,7 +1455,7 @@ soc:
|
|||||||
airgap:
|
airgap:
|
||||||
- repo: file:///nsm/rules/detect-sigma/repos/securityonion-resources
|
- repo: file:///nsm/rules/detect-sigma/repos/securityonion-resources
|
||||||
license: Elastic-2.0
|
license: Elastic-2.0
|
||||||
folder: sigma/stable
|
folder: sigma
|
||||||
community: true
|
community: true
|
||||||
rulesetName: securityonion-resources
|
rulesetName: securityonion-resources
|
||||||
- repo: file:///nsm/rules/custom-local-repos/local-sigma
|
- repo: file:///nsm/rules/custom-local-repos/local-sigma
|
||||||
@@ -1465,6 +1466,9 @@ soc:
|
|||||||
- core
|
- core
|
||||||
- emerging_threats_addon
|
- emerging_threats_addon
|
||||||
useEsql: false
|
useEsql: false
|
||||||
|
esqlCaseInsensitive: true
|
||||||
|
esqlQueryDelaySeconds: 30
|
||||||
|
esqlCorrelationAllowanceSeconds: 600
|
||||||
elastic:
|
elastic:
|
||||||
hostUrl:
|
hostUrl:
|
||||||
remoteHostUrls: []
|
remoteHostUrls: []
|
||||||
@@ -1492,6 +1496,9 @@ soc:
|
|||||||
org: Security Onion
|
org: Security Onion
|
||||||
bucket: telegraf/so_short_term
|
bucket: telegraf/so_short_term
|
||||||
verifyCert: false
|
verifyCert: false
|
||||||
|
notification:
|
||||||
|
dismissedPruneDays: 30
|
||||||
|
enabled: true
|
||||||
playbook:
|
playbook:
|
||||||
autoUpdateEnabled: true
|
autoUpdateEnabled: true
|
||||||
playbookImportFrequencySeconds: 86400
|
playbookImportFrequencySeconds: 86400
|
||||||
@@ -1537,7 +1544,7 @@ soc:
|
|||||||
Orchestrator: sonnet@SOAI
|
Orchestrator: sonnet@SOAI
|
||||||
Investigator: gemma@SOAI
|
Investigator: gemma@SOAI
|
||||||
DetectionEngineer: gemma@SOAI
|
DetectionEngineer: gemma@SOAI
|
||||||
useMemory: true
|
useMemory: false
|
||||||
useMemoryScanner: false
|
useMemoryScanner: false
|
||||||
dontScanBefore: ""
|
dontScanBefore: ""
|
||||||
memoryScanIntervalSeconds: 300
|
memoryScanIntervalSeconds: 300
|
||||||
@@ -1556,6 +1563,77 @@ soc:
|
|||||||
reconcilePersona: ""
|
reconcilePersona: ""
|
||||||
toolUseTurnAttempts: 12
|
toolUseTurnAttempts: 12
|
||||||
toolUseTurnDelayMs: 175
|
toolUseTurnDelayMs: 175
|
||||||
|
agentSessionMaxTurns: 20
|
||||||
|
agentStreamFlushIntervalMs: 1000
|
||||||
|
agentStreamIdleTimeoutSeconds: 300
|
||||||
|
automationSettings:
|
||||||
|
tickIntervalSeconds: 60
|
||||||
|
maxConcurrentItems: 4
|
||||||
|
maxQueuedItems: 0
|
||||||
|
alertTriageEpoch: "2026-09-24T00:00:00Z"
|
||||||
|
tools:
|
||||||
|
filterEventFields:
|
||||||
|
- "@timestamp"
|
||||||
|
- "client.name"
|
||||||
|
- "destination.ip"
|
||||||
|
- "destination.port"
|
||||||
|
- "destination.geo.country_name"
|
||||||
|
- "dns.query.name"
|
||||||
|
- "dns.query_name"
|
||||||
|
- "event.action"
|
||||||
|
- "event.category"
|
||||||
|
- "event.module"
|
||||||
|
- "event.dataset"
|
||||||
|
- "event.outcome"
|
||||||
|
- "event.severity"
|
||||||
|
- "event.severity_label"
|
||||||
|
- "event.type"
|
||||||
|
- "event_data.agent.name"
|
||||||
|
- "event_data.host.os.name"
|
||||||
|
- "file.mime_type"
|
||||||
|
- "file.name"
|
||||||
|
- "hash.md5"
|
||||||
|
- "hash.sha1"
|
||||||
|
- "host.mac"
|
||||||
|
- "host.name"
|
||||||
|
- "host.os.name"
|
||||||
|
- "http.method"
|
||||||
|
- "http.useragent"
|
||||||
|
- "http.virtual_host"
|
||||||
|
- "log.id.uid"
|
||||||
|
- "network.community_id"
|
||||||
|
- "network.protocol"
|
||||||
|
- "network.transport"
|
||||||
|
- "notice.message"
|
||||||
|
- "observer.name"
|
||||||
|
- "process.name"
|
||||||
|
- "process.executable"
|
||||||
|
- "process.entity_id"
|
||||||
|
- "process.command_line"
|
||||||
|
- "process.Ext.ancestry"
|
||||||
|
- "process.parent.entity_id"
|
||||||
|
- "process.parent.command_line"
|
||||||
|
- "rule.category"
|
||||||
|
- "rule.name"
|
||||||
|
- "rule.uuid"
|
||||||
|
- "software.name"
|
||||||
|
- "software.type"
|
||||||
|
- "software.version.unparsed"
|
||||||
|
- "source.ip"
|
||||||
|
- "source.port"
|
||||||
|
- "source.geo.country_name"
|
||||||
|
- "ssh.cypher_algorithm"
|
||||||
|
- "ssh.client"
|
||||||
|
- "ssh.server"
|
||||||
|
- "ssl.cipher"
|
||||||
|
- "ssl.server_name"
|
||||||
|
- "ssl.version"
|
||||||
|
- "system.auth.sudo.command"
|
||||||
|
- "user.name"
|
||||||
|
- "user.domain"
|
||||||
|
- "user.effective.name"
|
||||||
|
- "weird.name"
|
||||||
|
- "tags"
|
||||||
onionconfig:
|
onionconfig:
|
||||||
saltstackDir: /opt/so/saltstack
|
saltstackDir: /opt/so/saltstack
|
||||||
bypassEnabled: false
|
bypassEnabled: false
|
||||||
@@ -2603,8 +2681,11 @@ soc:
|
|||||||
query: "so_detection.language:suricata | groupby so_detection.ruleset so_detection.isEnabled | groupby so_detection.category"
|
query: "so_detection.language:suricata | groupby so_detection.ruleset so_detection.isEnabled | groupby so_detection.category"
|
||||||
description: Show all NIDS Detections, which are run with Suricata
|
description: Show all NIDS Detections, which are run with Suricata
|
||||||
- name: "Detection Type - Sigma (Elastalert) - All"
|
- name: "Detection Type - Sigma (Elastalert) - All"
|
||||||
query: "so_detection.language:sigma | groupby so_detection.ruleset so_detection.isEnabled | groupby so_detection.category | groupby so_detection.product"
|
query: "so_detection.language:sigma | groupby so_detection.ruleType | groupby so_detection.ruleset so_detection.isEnabled | groupby so_detection.category | groupby so_detection.product"
|
||||||
description: Show all Sigma Detections, which are run with Elastalert
|
description: Show all Sigma Detections, which are run with Elastalert
|
||||||
|
- name: "Detection Type - Sigma (Elastalert) - Correlations"
|
||||||
|
query: "so_detection.ruleType:correlation | groupby so_detection.correlationType so_detection.isEnabled | groupby so_detection.correlationTimespan | groupby so_detection.ruleset"
|
||||||
|
description: Show Sigma correlation Detections
|
||||||
- name: "Detection Type - YARA (Strelka)"
|
- name: "Detection Type - YARA (Strelka)"
|
||||||
query: "so_detection.language:yara | groupby so_detection.ruleset so_detection.isEnabled"
|
query: "so_detection.language:yara | groupby so_detection.ruleset so_detection.isEnabled"
|
||||||
description: Show all YARA detections, which are used by Strelka
|
description: Show all YARA detections, which are used by Strelka
|
||||||
@@ -2688,7 +2769,7 @@ soc:
|
|||||||
elastalert: |
|
elastalert: |
|
||||||
# This is a Sigma rule template, which uses YAML. Replace all template values with your own values.
|
# This is a Sigma rule template, which uses YAML. Replace all template values with your own values.
|
||||||
# The id (UUIDv4) is pregenerated and can safely be used.
|
# The id (UUIDv4) is pregenerated and can safely be used.
|
||||||
# Click "Convert" to convert the Sigma rule to use Security Onion field mappings within an EQL query
|
# Click "Convert" to convert the Sigma rule to use Security Onion field mappings within a backend query
|
||||||
#
|
#
|
||||||
# Rule Creation Guide: https://github.com/SigmaHQ/sigma/wiki/Rule-Creation-High%E2%80%90Level-Guide
|
# Rule Creation Guide: https://github.com/SigmaHQ/sigma/wiki/Rule-Creation-High%E2%80%90Level-Guide
|
||||||
# Logsources: https://sigmahq.io/docs/basics/log-sources.html
|
# Logsources: https://sigmahq.io/docs/basics/log-sources.html
|
||||||
@@ -2718,6 +2799,58 @@ soc:
|
|||||||
- ' -priv'
|
- ' -priv'
|
||||||
condition: all of selection_*
|
condition: all of selection_*
|
||||||
level: 'high' # info | low | medium | high | critical
|
level: 'high' # info | low | medium | high | critical
|
||||||
|
elastalert_correlation: |
|
||||||
|
# Sigma correlation rule; requires ES|QL (useEsql).
|
||||||
|
# First document: the correlation. Following documents: the rules it references.
|
||||||
|
#
|
||||||
|
# Types: event_count, value_count, temporal, value_sum, value_avg, value_median, value_percentile.
|
||||||
|
# Correlation Guide: https://sigmahq.io/docs/meta/correlations.html
|
||||||
|
# Logsources: https://sigmahq.io/docs/basics/log-sources.html
|
||||||
|
|
||||||
|
title: 'A Short Capitalized Title With Less Than 50 Characters'
|
||||||
|
id: [publicId]
|
||||||
|
status: 'experimental'
|
||||||
|
description: |
|
||||||
|
Describe what the correlation finds and, importantly, why relating these
|
||||||
|
events is more meaningful than either of them alone.
|
||||||
|
references:
|
||||||
|
- 'https://local.invalid'
|
||||||
|
author: '@SecurityOnion'
|
||||||
|
date: '[today]'
|
||||||
|
tags:
|
||||||
|
- detection.threat_hunting
|
||||||
|
- attack.technique_id
|
||||||
|
correlation:
|
||||||
|
type: value_count
|
||||||
|
rules:
|
||||||
|
- example_base_rule # the 'name' of the rule below
|
||||||
|
group-by:
|
||||||
|
- source.ip
|
||||||
|
# Xs, Xm, Xh, Xd or Xw.
|
||||||
|
timespan: 10m
|
||||||
|
condition:
|
||||||
|
field: dns.query.name
|
||||||
|
gte: 40
|
||||||
|
falsepositives:
|
||||||
|
- 'Describe the benign activity that also produces this pattern'
|
||||||
|
# Placeholders: %count%, %start%, %end%, %duration%, or any field.
|
||||||
|
summary: '%count% distinct names queried by %source.ip% in %duration%'
|
||||||
|
level: 'medium' # info | low | medium | high | critical
|
||||||
|
---
|
||||||
|
title: 'Base Event'
|
||||||
|
# referenced by 'name' (or 'id')
|
||||||
|
name: example_base_rule
|
||||||
|
description: 'The single event that the correlation aggregates.'
|
||||||
|
logsource:
|
||||||
|
category: network
|
||||||
|
service: dns
|
||||||
|
detection:
|
||||||
|
selection:
|
||||||
|
dns.query.name|exists: true
|
||||||
|
condition: selection
|
||||||
|
# Carried into the alert.
|
||||||
|
fields:
|
||||||
|
- dns.query.name
|
||||||
assistant:
|
assistant:
|
||||||
enabled: false
|
enabled: false
|
||||||
investigationPrompt: Investigate Alert ID {socId}
|
investigationPrompt: Investigate Alert ID {socId}
|
||||||
@@ -2731,7 +2864,7 @@ soc:
|
|||||||
- id: sonnet
|
- id: sonnet
|
||||||
displayName: Claude Sonnet
|
displayName: Claude Sonnet
|
||||||
origin: USA
|
origin: USA
|
||||||
contextLimitSmall: 200000
|
contextLimitSmall: 1000000
|
||||||
contextLimitLarge: 1000000
|
contextLimitLarge: 1000000
|
||||||
lowBalanceColorAlert: 500000
|
lowBalanceColorAlert: 500000
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ hypervisor_annotation:
|
|||||||
- name: /opt/so/saltstack/default/salt/hypervisor/soc_hypervisor.yaml
|
- name: /opt/so/saltstack/default/salt/hypervisor/soc_hypervisor.yaml
|
||||||
- source: salt://soc/dyanno/hypervisor/soc_hypervisor.yaml.jinja
|
- source: salt://soc/dyanno/hypervisor/soc_hypervisor.yaml.jinja
|
||||||
- template: jinja
|
- template: jinja
|
||||||
- user: socore
|
- user: root
|
||||||
- group: socore
|
- group: root
|
||||||
- defaults:
|
- defaults:
|
||||||
HYPERVISORS: {{ HYPERVISORS }}
|
HYPERVISORS: {{ HYPERVISORS }}
|
||||||
baseDomainStatus: {{ salt['pillar.get']('baseDomain:status', 'Initialized') }}
|
baseDomainStatus: {{ salt['pillar.get']('baseDomain:status', 'Initialized') }}
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ so-soc:
|
|||||||
- name: so-soc
|
- name: so-soc
|
||||||
- networks:
|
- networks:
|
||||||
- sobridge:
|
- sobridge:
|
||||||
- ipv4_address: {{ DOCKERMERGED.containers['so-soc'].ip }}
|
- ipv4_address: {{ DOCKERMERGED.containers['so-soc'].ips['sobridge'] }}
|
||||||
|
- soauth:
|
||||||
|
- ipv4_address: {{ DOCKERMERGED.containers['so-soc'].ips['soauth'] }}
|
||||||
- binds:
|
- binds:
|
||||||
- /nsm/rules:/nsm/rules:rw
|
- /nsm/rules:/nsm/rules:rw
|
||||||
- /opt/so/conf/strelka:/opt/sensoroni/yara:rw
|
- /opt/so/conf/strelka:/opt/sensoroni/yara:rw
|
||||||
@@ -45,9 +47,8 @@ so-soc:
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
- /opt/so/conf/soc/motd.md:/opt/sensoroni/html/motd.md:ro
|
- /opt/so/conf/soc/motd.md:/opt/sensoroni/html/motd.md:ro
|
||||||
- /opt/so/conf/soc/banner.md:/opt/sensoroni/html/login/banner.md:ro
|
- /opt/so/conf/soc/banner.md:/opt/sensoroni/html/login/banner.md:ro
|
||||||
- /opt/so/conf/soc/sigma_so_pipeline.yaml:/opt/sensoroni/sigma_so_pipeline.yaml:ro
|
- /opt/so/conf/soc/sigma_pipelines:/opt/sensoroni/sigma_pipelines:ro
|
||||||
- /opt/so/conf/soc/sigma_playbook_pipeline.yaml:/opt/sensoroni/sigma_playbook_pipeline.yaml:ro
|
- /opt/so/conf/soc/sigma_playbook_pipeline.yaml:/opt/sensoroni/sigma_playbook_pipeline.yaml:ro
|
||||||
- /opt/so/conf/soc/sigma_final_pipeline.yaml:/opt/sensoroni/sigma_final_pipeline.yaml:ro
|
|
||||||
- /opt/so/conf/soc/playbook_placeholder_map.yaml:/opt/sensoroni/playbook_placeholder_map.yaml:ro
|
- /opt/so/conf/soc/playbook_placeholder_map.yaml:/opt/sensoroni/playbook_placeholder_map.yaml:ro
|
||||||
- /opt/so/conf/soc/playbook_placeholder_map_custom.yaml:/opt/sensoroni/playbook_placeholder_map_custom.yaml:ro
|
- /opt/so/conf/soc/playbook_placeholder_map_custom.yaml:/opt/sensoroni/playbook_placeholder_map_custom.yaml:ro
|
||||||
- /opt/so/conf/soc/custom.js:/opt/sensoroni/html/js/custom.js:ro
|
- /opt/so/conf/soc/custom.js:/opt/sensoroni/html/js/custom.js:ro
|
||||||
@@ -105,6 +106,7 @@ so-soc:
|
|||||||
- file: socclientsroles
|
- file: socclientsroles
|
||||||
- file: socplaybookplaceholdermap
|
- file: socplaybookplaceholdermap
|
||||||
- file: socplaybookplaceholdermapcustom
|
- file: socplaybookplaceholdermapcustom
|
||||||
|
- file: socsigmapipelines
|
||||||
|
|
||||||
delete_so-soc_so-status.disabled:
|
delete_so-soc_so-status.disabled:
|
||||||
file.uncomment:
|
file.uncomment:
|
||||||
|
|||||||
@@ -0,0 +1,484 @@
|
|||||||
|
name: Security Onion ES|QL Pipeline
|
||||||
|
# ES|QL query settings
|
||||||
|
priority: 92
|
||||||
|
transformations:
|
||||||
|
- id: esql_default_index
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val: .ds-logs-*
|
||||||
|
- id: esql_source_metadata
|
||||||
|
type: set_state
|
||||||
|
key: metadata
|
||||||
|
val: "_id, _index, _source"
|
||||||
|
- id: esql_source_keep
|
||||||
|
type: set_state
|
||||||
|
key: keep
|
||||||
|
val: "_id, _index, _source"
|
||||||
|
# unmapped fields read as null instead of failing the query
|
||||||
|
- id: esql_unmapped_fields
|
||||||
|
type: set_state
|
||||||
|
key: unmapped_fields
|
||||||
|
val: nullify
|
||||||
|
# FROM targets per logsource, any namespace; later entries win, correlations get the union
|
||||||
|
- id: esql_index_process_creation
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-endpoint.events.process-*
|
||||||
|
- .ds-logs-windows.sysmon_operational-*
|
||||||
|
- .ds-logs-system.security-*
|
||||||
|
- .ds-logs-windows.powershell-*
|
||||||
|
- .ds-logs-windows.forwarded-*
|
||||||
|
- .ds-logs-sysmon_linux.log-*
|
||||||
|
- .ds-logs-auditd_manager.auditd-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
category: process_creation
|
||||||
|
- id: esql_index_process_creation_windows
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-endpoint.events.process-*
|
||||||
|
- .ds-logs-windows.sysmon_operational-*
|
||||||
|
- .ds-logs-system.security-*
|
||||||
|
- .ds-logs-windows.powershell-*
|
||||||
|
- .ds-logs-windows.forwarded-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
product: windows
|
||||||
|
category: process_creation
|
||||||
|
- id: esql_index_process_creation_linux
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-endpoint.events.process-*
|
||||||
|
- .ds-logs-sysmon_linux.log-*
|
||||||
|
- .ds-logs-auditd_manager.auditd-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
product: linux
|
||||||
|
category: process_creation
|
||||||
|
- id: esql_index_process_creation_macos
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-endpoint.events.process-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
product: macos
|
||||||
|
category: process_creation
|
||||||
|
- id: esql_index_file
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-endpoint.events.file-*
|
||||||
|
- .ds-logs-windows.sysmon_operational-*
|
||||||
|
- .ds-logs-windows.forwarded-*
|
||||||
|
- .ds-logs-sysmon_linux.log-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_cond_op: or
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
category: file_event
|
||||||
|
- type: logsource
|
||||||
|
category: file_delete
|
||||||
|
- type: logsource
|
||||||
|
category: file_rename
|
||||||
|
- type: logsource
|
||||||
|
category: file_change
|
||||||
|
- type: logsource
|
||||||
|
category: file_access
|
||||||
|
- id: esql_index_file_windows
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-endpoint.events.file-*
|
||||||
|
- .ds-logs-windows.sysmon_operational-*
|
||||||
|
- .ds-logs-windows.forwarded-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_cond_op: or
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
product: windows
|
||||||
|
category: file_event
|
||||||
|
- type: logsource
|
||||||
|
product: windows
|
||||||
|
category: file_delete
|
||||||
|
- type: logsource
|
||||||
|
product: windows
|
||||||
|
category: file_rename
|
||||||
|
- type: logsource
|
||||||
|
product: windows
|
||||||
|
category: file_change
|
||||||
|
- type: logsource
|
||||||
|
product: windows
|
||||||
|
category: file_access
|
||||||
|
- id: esql_index_file_linux
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-endpoint.events.file-*
|
||||||
|
- .ds-logs-sysmon_linux.log-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_cond_op: or
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
product: linux
|
||||||
|
category: file_event
|
||||||
|
- type: logsource
|
||||||
|
product: linux
|
||||||
|
category: file_delete
|
||||||
|
- type: logsource
|
||||||
|
product: linux
|
||||||
|
category: file_rename
|
||||||
|
- type: logsource
|
||||||
|
product: linux
|
||||||
|
category: file_change
|
||||||
|
- type: logsource
|
||||||
|
product: linux
|
||||||
|
category: file_access
|
||||||
|
- id: esql_index_file_macos
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-endpoint.events.file-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_cond_op: or
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
product: macos
|
||||||
|
category: file_event
|
||||||
|
- type: logsource
|
||||||
|
product: macos
|
||||||
|
category: file_delete
|
||||||
|
- type: logsource
|
||||||
|
product: macos
|
||||||
|
category: file_rename
|
||||||
|
- type: logsource
|
||||||
|
product: macos
|
||||||
|
category: file_change
|
||||||
|
- type: logsource
|
||||||
|
product: macos
|
||||||
|
category: file_access
|
||||||
|
- id: esql_index_registry
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-endpoint.events.registry-*
|
||||||
|
- .ds-logs-windows.sysmon_operational-*
|
||||||
|
- .ds-logs-windows.forwarded-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_cond_op: or
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
category: registry_set
|
||||||
|
- type: logsource
|
||||||
|
category: registry_add
|
||||||
|
- type: logsource
|
||||||
|
category: registry_delete
|
||||||
|
- type: logsource
|
||||||
|
category: registry_event
|
||||||
|
- id: esql_index_library
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-endpoint.events.library-*
|
||||||
|
- .ds-logs-windows.sysmon_operational-*
|
||||||
|
- .ds-logs-windows.forwarded-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_cond_op: or
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
category: image_load
|
||||||
|
- type: logsource
|
||||||
|
category: driver_load
|
||||||
|
- id: esql_index_endpoint_network
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-endpoint.events.network-*
|
||||||
|
- .ds-logs-windows.sysmon_operational-*
|
||||||
|
- .ds-logs-windows.forwarded-*
|
||||||
|
- .ds-logs-sysmon_linux.log-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_cond_op: or
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
category: network_connection
|
||||||
|
- type: logsource
|
||||||
|
category: dns_query
|
||||||
|
- id: esql_index_endpoint_network_windows
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-endpoint.events.network-*
|
||||||
|
- .ds-logs-windows.sysmon_operational-*
|
||||||
|
- .ds-logs-windows.forwarded-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_cond_op: or
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
product: windows
|
||||||
|
category: network_connection
|
||||||
|
- type: logsource
|
||||||
|
product: windows
|
||||||
|
category: dns_query
|
||||||
|
- id: esql_index_endpoint_network_linux
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-endpoint.events.network-*
|
||||||
|
- .ds-logs-sysmon_linux.log-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_cond_op: or
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
product: linux
|
||||||
|
category: network_connection
|
||||||
|
- type: logsource
|
||||||
|
product: linux
|
||||||
|
category: dns_query
|
||||||
|
- id: esql_index_endpoint_network_macos
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-endpoint.events.network-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_cond_op: or
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
product: macos
|
||||||
|
category: network_connection
|
||||||
|
- type: logsource
|
||||||
|
product: macos
|
||||||
|
category: dns_query
|
||||||
|
- id: esql_index_sysmon_only
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-windows.sysmon_operational-*
|
||||||
|
- .ds-logs-windows.forwarded-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_cond_op: or
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
category: process_access
|
||||||
|
- type: logsource
|
||||||
|
category: create_remote_thread
|
||||||
|
- type: logsource
|
||||||
|
category: pipe_created
|
||||||
|
- type: logsource
|
||||||
|
category: create_stream_hash
|
||||||
|
- type: logsource
|
||||||
|
category: wmi_event
|
||||||
|
- type: logsource
|
||||||
|
category: raw_access_thread
|
||||||
|
- type: logsource
|
||||||
|
category: process_tampering
|
||||||
|
- type: logsource
|
||||||
|
category: sysmon_status
|
||||||
|
- type: logsource
|
||||||
|
category: sysmon_error
|
||||||
|
- type: logsource
|
||||||
|
category: file_executable_detected
|
||||||
|
- type: logsource
|
||||||
|
category: file_block_executable
|
||||||
|
- type: logsource
|
||||||
|
category: file_block_shredding
|
||||||
|
- type: logsource
|
||||||
|
category: clipboard_capture
|
||||||
|
- type: logsource
|
||||||
|
product: windows
|
||||||
|
service: sysmon
|
||||||
|
- id: esql_index_ps_operational
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-windows.powershell_operational-*
|
||||||
|
- .ds-logs-windows.forwarded-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_cond_op: or
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
category: ps_script
|
||||||
|
- type: logsource
|
||||||
|
category: ps_module
|
||||||
|
- type: logsource
|
||||||
|
product: windows
|
||||||
|
service: powershell
|
||||||
|
- id: esql_index_ps_classic
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-windows.powershell-*
|
||||||
|
- .ds-logs-windows.forwarded-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_cond_op: or
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
category: ps_classic_start
|
||||||
|
- type: logsource
|
||||||
|
category: ps_classic_provider_start
|
||||||
|
- type: logsource
|
||||||
|
category: ps_classic_script
|
||||||
|
- type: logsource
|
||||||
|
product: windows
|
||||||
|
service: powershell-classic
|
||||||
|
- id: esql_index_win_security
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-system.security-*
|
||||||
|
- .ds-logs-windows.forwarded-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
product: windows
|
||||||
|
service: security
|
||||||
|
- id: esql_index_win_system
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-system.system-*
|
||||||
|
- .ds-logs-windows.forwarded-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
product: windows
|
||||||
|
service: system
|
||||||
|
- id: esql_index_win_application
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-system.application-*
|
||||||
|
- .ds-logs-windows.forwarded-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
product: windows
|
||||||
|
service: application
|
||||||
|
- id: esql_index_linux_auth
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-system.auth-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_cond_op: or
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
product: linux
|
||||||
|
service: auth
|
||||||
|
- type: logsource
|
||||||
|
product: linux
|
||||||
|
service: sshd
|
||||||
|
- type: logsource
|
||||||
|
product: linux
|
||||||
|
service: sudo
|
||||||
|
- id: esql_index_linux_syslog
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-system.syslog-*
|
||||||
|
- .ds-logs-syslog-so-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
product: linux
|
||||||
|
service: syslog
|
||||||
|
- id: esql_index_linux_auditd
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-auditd_manager.auditd-*
|
||||||
|
- .ds-logs-auditd.log-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
product: linux
|
||||||
|
service: auditd
|
||||||
|
- id: esql_index_network
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-zeek-so-*
|
||||||
|
- .ds-logs-suricata-so-*
|
||||||
|
- .ds-logs-suricata.alerts-so-*
|
||||||
|
- .ds-logs-endpoint.events.network-*
|
||||||
|
- .ds-logs-windows.sysmon_operational-*
|
||||||
|
- .ds-logs-sysmon_linux.log-*
|
||||||
|
- .ds-logs-windows.forwarded-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
category: network
|
||||||
|
- id: esql_index_so_network
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-zeek-so-*
|
||||||
|
- .ds-logs-suricata-so-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_cond_op: or
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
category: network
|
||||||
|
service: connection
|
||||||
|
- type: logsource
|
||||||
|
category: network
|
||||||
|
service: dns
|
||||||
|
- type: logsource
|
||||||
|
category: network
|
||||||
|
service: http
|
||||||
|
- type: logsource
|
||||||
|
category: network
|
||||||
|
service: file
|
||||||
|
- type: logsource
|
||||||
|
category: network
|
||||||
|
service: x509
|
||||||
|
- type: logsource
|
||||||
|
category: network
|
||||||
|
service: ssl
|
||||||
|
- type: logsource
|
||||||
|
category: network
|
||||||
|
service: ssh
|
||||||
|
- type: logsource
|
||||||
|
category: dns
|
||||||
|
- id: esql_index_zeek
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-zeek-so-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
product: zeek
|
||||||
|
- id: esql_index_opencanary
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-idh-so-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
product: opencanary
|
||||||
|
- id: esql_index_kratos
|
||||||
|
type: set_state
|
||||||
|
key: index
|
||||||
|
val:
|
||||||
|
- .ds-logs-kratos-so-*
|
||||||
|
- .ds-logs-import-so-*
|
||||||
|
rule_conditions:
|
||||||
|
- type: logsource
|
||||||
|
product: kratos
|
||||||
|
|
||||||
|
# SOC reads the mapped group-by columns from this output
|
||||||
|
postprocessing:
|
||||||
|
- id: esql_correlation_group_by
|
||||||
|
type: template
|
||||||
|
template: '{{ {"query": query, "group_by": rule.group_by} | tojson }}'
|
||||||
|
rule_conditions:
|
||||||
|
- type: is_sigma_correlation_rule
|
||||||
+35
@@ -1,6 +1,38 @@
|
|||||||
name: Security Onion Baseline Pipeline
|
name: Security Onion Baseline Pipeline
|
||||||
priority: 90
|
priority: 90
|
||||||
transformations:
|
transformations:
|
||||||
|
# ES|QL scalar == returns null on multivalued fields; the
|
||||||
|
# backend reads this key and emits MV_INTERSECTS instead.
|
||||||
|
- id: declare_multivalue_fields
|
||||||
|
type: set_state
|
||||||
|
key: multivalue_fields
|
||||||
|
val:
|
||||||
|
- event.type
|
||||||
|
- event.action
|
||||||
|
- event.category
|
||||||
|
- tags
|
||||||
|
- process.args
|
||||||
|
- related.ip
|
||||||
|
- dns.resolved_ip
|
||||||
|
# always lowercase: matched exactly with the indexed ':' operator
|
||||||
|
- id: case_sensitive_categorization_fields
|
||||||
|
type: set_state
|
||||||
|
key: case_insensitive_exempt_fields
|
||||||
|
val:
|
||||||
|
- tags
|
||||||
|
- event.category
|
||||||
|
- event.type
|
||||||
|
- event.kind
|
||||||
|
# Not every source maps .caseless; EQL/ES|QL already match case-insensitively.
|
||||||
|
- id: caseless_to_parent_fields
|
||||||
|
type: field_name_mapping
|
||||||
|
mapping:
|
||||||
|
process.executable.caseless: process.executable
|
||||||
|
process.name.caseless: process.name
|
||||||
|
process.parent.executable.caseless: process.parent.executable
|
||||||
|
process.parent.name.caseless: process.parent.name
|
||||||
|
target.process.executable.caseless: target.process.executable
|
||||||
|
target.process.name.caseless: target.process.name
|
||||||
- id: baseline_field_name_mapping
|
- id: baseline_field_name_mapping
|
||||||
type: field_name_mapping
|
type: field_name_mapping
|
||||||
mapping:
|
mapping:
|
||||||
@@ -95,6 +127,9 @@ transformations:
|
|||||||
valid_hash_algos: ["MD5", "SHA1", "SHA256", "SHA512", "IMPHASH"]
|
valid_hash_algos: ["MD5", "SHA1", "SHA256", "SHA512", "IMPHASH"]
|
||||||
field_prefix: "file"
|
field_prefix: "file"
|
||||||
drop_algo_prefix: False
|
drop_algo_prefix: False
|
||||||
|
# ecs_windows renamed Hashes; pySigma 1.5+ parses only these
|
||||||
|
field_to_parse:
|
||||||
|
- winlog.event_data.Hashes
|
||||||
field_name_conditions:
|
field_name_conditions:
|
||||||
- type: include_fields
|
- type: include_fields
|
||||||
fields:
|
fields:
|
||||||
@@ -2,13 +2,13 @@ name: Security Onion - Playbook Pipeline
|
|||||||
priority: 97
|
priority: 97
|
||||||
transformations:
|
transformations:
|
||||||
# Route to lowercase-normalized .caseless subfields for case-insensitive matching.
|
# Route to lowercase-normalized .caseless subfields for case-insensitive matching.
|
||||||
# file.path.caseless exists on Defend only (Sysmon file events lack it);
|
|
||||||
# registry.path / dll.path / file.name have no .caseless on any source.
|
# registry.path / dll.path / file.name have no .caseless on any source.
|
||||||
- id: case_insensitive_string_fields
|
- id: case_insensitive_string_fields
|
||||||
type: field_name_mapping
|
type: field_name_mapping
|
||||||
mapping:
|
mapping:
|
||||||
process.executable: process.executable.caseless
|
process.executable: process.executable.caseless
|
||||||
process.parent.executable: process.parent.executable.caseless
|
process.parent.executable: process.parent.executable.caseless
|
||||||
|
process.parent.name: process.parent.name.caseless
|
||||||
process.command_line: process.command_line.caseless
|
process.command_line: process.command_line.caseless
|
||||||
process.parent.command_line: process.parent.command_line.caseless
|
process.parent.command_line: process.parent.command_line.caseless
|
||||||
file.path: file.path.caseless
|
file.path: file.path.caseless
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
{% from 'elasticsearch/config.map.jinja' import ELASTICSEARCH_NODES %}
|
{% from 'elasticsearch/config.map.jinja' import ELASTICSEARCH_NODES %}
|
||||||
{% from 'manager/map.jinja' import MANAGERMERGED %}
|
{% from 'manager/map.jinja' import MANAGERMERGED %}
|
||||||
{% from 'telegraf/map.jinja' import TELEGRAFMERGED %}
|
{% from 'telegraf/map.jinja' import TELEGRAFMERGED %}
|
||||||
|
{% from 'elastalert/map.jinja' import ELASTALERTMERGED %}
|
||||||
{%- set PG_ENTRY = salt['pillar.get']('telegraf:postgres_creds:' ~ grains.id, {}) %}
|
{%- set PG_ENTRY = salt['pillar.get']('telegraf:postgres_creds:' ~ grains.id, {}) %}
|
||||||
{%- set PG_USER = PG_ENTRY.get('user', '') %}
|
{%- set PG_USER = PG_ENTRY.get('user', '') %}
|
||||||
{%- set PG_PASS = PG_ENTRY.get('pass', '') %}
|
{%- set PG_PASS = PG_ENTRY.get('pass', '') %}
|
||||||
@@ -63,6 +64,10 @@
|
|||||||
{% do SOCMERGED.config.server.modules.elastalertengine.update({'enabledSigmaRules': SOCMERGED.config.server.modules.elastalertengine.enabledSigmaRules.default}) %}
|
{% do SOCMERGED.config.server.modules.elastalertengine.update({'enabledSigmaRules': SOCMERGED.config.server.modules.elastalertengine.enabledSigmaRules.default}) %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{# correlation schedules follow ElastAlert's run_every #}
|
||||||
|
{% set run_every = ELASTALERTMERGED.config.run_every %}
|
||||||
|
{% do SOCMERGED.config.server.modules.elastalertengine.update({'elastAlertRunEverySeconds': run_every.get('minutes', 0) * 60 + run_every.get('seconds', 0)}) %}
|
||||||
|
|
||||||
{# set elastalertengine.rulesRepos, strelkaengine.rulesRepos, and suricataengine.rulesetSources based on airgap or not #}
|
{# set elastalertengine.rulesRepos, strelkaengine.rulesRepos, and suricataengine.rulesetSources based on airgap or not #}
|
||||||
{% if GLOBALS.airgap %}
|
{% if GLOBALS.airgap %}
|
||||||
{% do SOCMERGED.config.server.modules.elastalertengine.update({'rulesRepos': SOCMERGED.config.server.modules.elastalertengine.rulesRepos.airgap}) %}
|
{% do SOCMERGED.config.server.modules.elastalertengine.update({'rulesRepos': SOCMERGED.config.server.modules.elastalertengine.rulesRepos.airgap}) %}
|
||||||
@@ -80,6 +85,12 @@
|
|||||||
{% do SOCMERGED.config.server.update({'airgapEnabled': false}) %}
|
{% do SOCMERGED.config.server.update({'airgapEnabled': false}) %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{# correlation authoring requires ES|QL #}
|
||||||
|
{% if not SOCMERGED.config.server.modules.elastalertengine.useEsql %}
|
||||||
|
{% do SOCMERGED.config.server.client.detection.templateDetections.pop('elastalert_correlation', None) %}
|
||||||
|
{% do SOCMERGED.config.server.client.detections.update({'queries': SOCMERGED.config.server.client.detections.queries | rejectattr('name', 'equalto', 'Detection Type - Sigma (Elastalert) - Correlations') | list}) %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{# Define the postgresmetrics module if telegraf is setup to only use Postgres #}
|
{# Define the postgresmetrics module if telegraf is setup to only use Postgres #}
|
||||||
{% if TELEGRAFMERGED.output != 'INFLUXDB' and PG_USER and PG_PASS %}
|
{% if TELEGRAFMERGED.output != 'INFLUXDB' and PG_USER and PG_PASS %}
|
||||||
{% do SOCMERGED.config.server.modules.update({
|
{% do SOCMERGED.config.server.modules.update({
|
||||||
|
|||||||
+147
-1
@@ -155,6 +155,15 @@ soc:
|
|||||||
description: Path to custom markdown templates for PDF report generation. All markdown files in this directory will be available as custom reports in the SOC Reports interface.
|
description: Path to custom markdown templates for PDF report generation. All markdown files in this directory will be available as custom reports in the SOC Reports interface.
|
||||||
global: True
|
global: True
|
||||||
advanced: True
|
advanced: True
|
||||||
|
schedules:
|
||||||
|
title: Schedules
|
||||||
|
description: Schedules that are shared across the Security Onion product. Modify via one of the SOC Schedules view.
|
||||||
|
readonlyUi: True
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
forcedType: string
|
||||||
|
syntax: json
|
||||||
|
storage: db
|
||||||
subgrids:
|
subgrids:
|
||||||
title: Subordinate Grids
|
title: Subordinate Grids
|
||||||
description: |
|
description: |
|
||||||
@@ -392,10 +401,27 @@ soc:
|
|||||||
advanced: False
|
advanced: False
|
||||||
helpLink: sigma
|
helpLink: sigma
|
||||||
useEsql:
|
useEsql:
|
||||||
description: "(Pre-release) Use Elasticsearch Piped Query Language (ES|QL) instead of EQL (Elastic Query Language) for Elasticsearch queries. The Sigma converter will output ES|QL instead of EQL, allowing support for correlations."
|
description: "(Pre-release) Use Elasticsearch Piped Query Language (ES|QL) instead of EQL (Elastic Query Language) for Elasticsearch queries. The Sigma converter will output ES|QL instead of EQL, allowing support for correlations. Switching back to EQL is not supported for correlations."
|
||||||
global: True
|
global: True
|
||||||
advanced: True
|
advanced: True
|
||||||
forcedType: bool
|
forcedType: bool
|
||||||
|
esqlCaseInsensitive:
|
||||||
|
description: "Match string values case-insensitively when converting Sigma rules, and group correlation values regardless of case. Applies to ES|QL only"
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
forcedType: bool
|
||||||
|
esqlQueryDelaySeconds:
|
||||||
|
description: "Seconds ES|QL rules search behind now, so unsearchable events aren't missed. Delays alerts by the same amount. Set at least the longest index refresh interval. ES|QL only."
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
forcedType: int
|
||||||
|
helpLink: sigma
|
||||||
|
esqlCorrelationAllowanceSeconds:
|
||||||
|
description: "Extra seconds of arrivals each correlation run re-reads beyond its timespan, so a burst whose events arrive spread out is still counted together. Correlations below a threshold (lt, lte, eq, neq) and value_avg or value_percentile correlations count only the timespan ending this much plus esqlQueryDelaySeconds ago, so they alert this much later. ES|QL only."
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
forcedType: int
|
||||||
|
helpLink: sigma
|
||||||
elastic:
|
elastic:
|
||||||
index:
|
index:
|
||||||
description: Comma-separated list of indices or index patterns (wildcard "*" supported) that SOC will search for records.
|
description: Comma-separated list of indices or index patterns (wildcard "*" supported) that SOC will search for records.
|
||||||
@@ -476,6 +502,29 @@ soc:
|
|||||||
global: True
|
global: True
|
||||||
advanced: True
|
advanced: True
|
||||||
forcedType: bool
|
forcedType: bool
|
||||||
|
notification:
|
||||||
|
destinations:
|
||||||
|
title: Notification Destinations
|
||||||
|
description: JSON list of notifications. Modify via the SOC Notifications view.
|
||||||
|
readonlyUi: True
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
forcedType: string
|
||||||
|
syntax: json
|
||||||
|
storage: db
|
||||||
|
dismissedPruneDays:
|
||||||
|
title: Dismissed Retention Days
|
||||||
|
description: The number of days to retain dismissed notifications. When a notification is dismissed, it will be pruned after this many days. Only one user need dismiss a notification for it to be pruned.
|
||||||
|
forcedType: int
|
||||||
|
global: True
|
||||||
|
maxListLimit:
|
||||||
|
description: Maximum number of notifications to display.
|
||||||
|
forcedType: int
|
||||||
|
global: True
|
||||||
|
enabled:
|
||||||
|
description: Enables or disables the SOC notification module.
|
||||||
|
forcedType: bool
|
||||||
|
global: True
|
||||||
postgres:
|
postgres:
|
||||||
host:
|
host:
|
||||||
description: Hostname or IP address of the PostgreSQL server used by SOC. Defaults to the manager hostname.
|
description: Hostname or IP address of the PostgreSQL server used by SOC. Defaults to the manager hostname.
|
||||||
@@ -502,6 +551,48 @@ soc:
|
|||||||
global: True
|
global: True
|
||||||
sensitive: True
|
sensitive: True
|
||||||
advanced: True
|
advanced: True
|
||||||
|
postgresmetrics:
|
||||||
|
host:
|
||||||
|
description: Hostname or IP address of the PostgreSQL server used by Telegraf. Defaults to the manager hostname.
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
port:
|
||||||
|
description: Port of the PostgreSQL server used by Telegraf.
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
sslMode:
|
||||||
|
description: "Use encrypted connections to the PostgreSQL server used by Telegraf. Must be one of the following values: disable, allow, prefer, require, verify-ca, verify-full."
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
database:
|
||||||
|
description: Database to authenticate to on the PostgreSQL server.
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
user:
|
||||||
|
description: Username to authenticate to the PostgreSQL server used by Telegraf.
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
password:
|
||||||
|
description: Password used to authenticate to the PostgreSQL server used by Telegraf.
|
||||||
|
global: True
|
||||||
|
sensitive: True
|
||||||
|
advanced: True
|
||||||
|
cacheExpirationMs:
|
||||||
|
description: The interval (in milliseconds) to wait before querying the DB for updated metrics.
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
maxMetricAgeSeconds:
|
||||||
|
description: The maximum age (in seconds) of metrics to display in the SOC Grid Metrics view. Metrics older than this value will not be displayed.
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
alarms:
|
||||||
|
description: JSON list of metric alarms. Modify via the SOC Grid Alarms view.
|
||||||
|
readonlyUi: True
|
||||||
|
advanced: True
|
||||||
|
global: True
|
||||||
|
forcedType: string
|
||||||
|
syntax: json
|
||||||
|
storage: db
|
||||||
salt:
|
salt:
|
||||||
longRelayTimeoutMs:
|
longRelayTimeoutMs:
|
||||||
description: Duration (in milliseconds) to wait for a response from the Salt API when executing tasks known for being long running before giving up and showing an error on the SOC UI.
|
description: Duration (in milliseconds) to wait for a response from the Salt API when executing tasks known for being long running before giving up and showing an error on the SOC UI.
|
||||||
@@ -760,6 +851,7 @@ soc:
|
|||||||
- gemini
|
- gemini
|
||||||
- openai_responses
|
- openai_responses
|
||||||
- openai_chat
|
- openai_chat
|
||||||
|
- openai_embeddings
|
||||||
- field: apiUrl
|
- field: apiUrl
|
||||||
label: API URL
|
label: API URL
|
||||||
required: False
|
required: False
|
||||||
@@ -781,6 +873,15 @@ soc:
|
|||||||
description: Indicates if the Assistant Module should operate in agentic mode or not. If true, agents can work together to solve tasks.
|
description: Indicates if the Assistant Module should operate in agentic mode or not. If true, agents can work together to solve tasks.
|
||||||
global: True
|
global: True
|
||||||
forcedType: bool
|
forcedType: bool
|
||||||
|
automations:
|
||||||
|
description: Scheduled automations for the Onion AI assistant, managed from the Agent Studio.
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
readonlyUi: True
|
||||||
|
storage: db
|
||||||
|
forcedType: string
|
||||||
|
syntax: json
|
||||||
|
helpLink: onion-ai
|
||||||
agents:
|
agents:
|
||||||
description: Agent definitions for the Onion AI assistant, managed from the Agent Studio. An entry naming a system agent overrides only the fields an admin may change; everything else comes from the built-in definition.
|
description: Agent definitions for the Onion AI assistant, managed from the Agent Studio. An entry naming a system agent overrides only the fields an admin may change; everything else comes from the built-in definition.
|
||||||
global: True
|
global: True
|
||||||
@@ -813,6 +914,9 @@ soc:
|
|||||||
- field: persona
|
- field: persona
|
||||||
label: Persona
|
label: Persona
|
||||||
multiline: True
|
multiline: True
|
||||||
|
- field: maxConcurrentInstances
|
||||||
|
label: Max Concurrent Instances
|
||||||
|
forcedType: int
|
||||||
skills:
|
skills:
|
||||||
description: Skill definitions for the Onion AI assistant, managed from the Agent Studio. An entry naming a system skill overrides only its enabled state and persona addendum; its tool set comes from the built-in definition.
|
description: Skill definitions for the Onion AI assistant, managed from the Agent Studio. An entry naming a system skill overrides only its enabled state and persona addendum; its tool set comes from the built-in definition.
|
||||||
global: True
|
global: True
|
||||||
@@ -916,6 +1020,48 @@ soc:
|
|||||||
description: The number of times to retry extracting memories from a session if errors occur.
|
description: The number of times to retry extracting memories from a session if errors occur.
|
||||||
global: True
|
global: True
|
||||||
advanced: True
|
advanced: True
|
||||||
|
agentSessionMaxTurns:
|
||||||
|
description: Maximum number of model turns a headless agent session, such as one started by an automation, may take before it is stopped. Turns taken by delegated sub-agents count toward this limit. A session that reaches the limit is recorded as failed.
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
forcedType: int
|
||||||
|
agentStreamFlushIntervalMs:
|
||||||
|
description: Milliseconds between writes of a streaming headless agent turn to the database. Lower values show progress sooner in the Agent Studio at the cost of more frequent Elasticsearch updates.
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
forcedType: int
|
||||||
|
agentStreamIdleTimeoutSeconds:
|
||||||
|
description: Seconds a streaming headless agent turn may go without receiving any output before it is abandoned and the session is recorded as failed. Set to 0 to disable the timeout.
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
forcedType: int
|
||||||
|
automationSettings:
|
||||||
|
tickIntervalSeconds:
|
||||||
|
description: How often, in seconds, the automation scheduler checks for automations that are due to run. Must be greater than 0. This value is also the default interval for new automations, however admins can override individual automation intervals to a longer value via the Agent Studio.
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
forcedType: int
|
||||||
|
maxConcurrentItems:
|
||||||
|
description: Maximum number of automation work items that may run at the same time. Additional work items wait in the queue until a running item finishes. User chat sessions count toward this limit but are never held back by it. Set to 0 to disable the limit.
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
forcedType: int
|
||||||
|
maxQueuedItems:
|
||||||
|
description: Maximum number of automation work items that may wait to start. Once the queue is full, no new work items are created until the backlog drains. Set to 0 to disable the limit.
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
forcedType: int
|
||||||
|
alertTriageEpoch:
|
||||||
|
description: The earliest alert time the Alert Triage automation will consider. Alerts before this time are never triaged, which keeps a first run on an existing deployment from working through old history. Must be in UTC format (2026-09-24T00:00:00Z).
|
||||||
|
regex: '^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z)?$'
|
||||||
|
regexFailureMessage: Expecting date in RFC3339 format (2026-09-24T00:00:00Z)
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
tools:
|
||||||
|
filterEventFields:
|
||||||
|
description: A whitelist of fields to return when OnionAI uses the query_events tool. All other fields are removed. One field per line.
|
||||||
|
global: True
|
||||||
|
multiline: True
|
||||||
client:
|
client:
|
||||||
assistant:
|
assistant:
|
||||||
enabled:
|
enabled:
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ strelka_sbin:
|
|||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://strelka/tools/sbin
|
- source: salt://strelka/tools/sbin
|
||||||
- user: 939
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ suricata:
|
|||||||
- gid: 940
|
- gid: 940
|
||||||
- home: /nsm/suricata
|
- home: /nsm/suricata
|
||||||
- createhome: False
|
- createhome: False
|
||||||
|
- shell: /sbin/nologin
|
||||||
|
|
||||||
socoregroupwithsuricata:
|
socoregroupwithsuricata:
|
||||||
group.present:
|
group.present:
|
||||||
@@ -76,16 +77,16 @@ suricata_sbin:
|
|||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://suricata/tools/sbin
|
- source: salt://suricata/tools/sbin
|
||||||
- user: 939
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
|
|
||||||
suricata_sbin_jinja:
|
suricata_sbin_jinja:
|
||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://suricata/tools/sbin_jinja
|
- source: salt://suricata/tools/sbin_jinja
|
||||||
- user: 939
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
- template: jinja
|
- template: jinja
|
||||||
|
|
||||||
|
|||||||
+92
-12
@@ -36,7 +36,7 @@ tgraf_sync_script_{{script}}:
|
|||||||
- name: /opt/so/conf/telegraf/scripts/{{script}}
|
- name: /opt/so/conf/telegraf/scripts/{{script}}
|
||||||
- user: root
|
- user: root
|
||||||
- group: 939
|
- group: 939
|
||||||
- mode: 770
|
- mode: 750
|
||||||
- template: jinja
|
- template: jinja
|
||||||
- source: salt://telegraf/scripts/{{script}}
|
- source: salt://telegraf/scripts/{{script}}
|
||||||
- defaults:
|
- defaults:
|
||||||
@@ -49,7 +49,7 @@ tgraf_sync_script_esindexsize.sh:
|
|||||||
- name: /opt/so/conf/telegraf/scripts/esindexsize.sh
|
- name: /opt/so/conf/telegraf/scripts/esindexsize.sh
|
||||||
- user: root
|
- user: root
|
||||||
- group: 939
|
- group: 939
|
||||||
- mode: 770
|
- mode: 750
|
||||||
- source: salt://telegraf/scripts/esindexsize.sh
|
- source: salt://telegraf/scripts/esindexsize.sh
|
||||||
{# Copy conf/elasticsearch/curl.config for telegraf to use with esindexsize.sh #}
|
{# Copy conf/elasticsearch/curl.config for telegraf to use with esindexsize.sh #}
|
||||||
tgraf_sync_escurl_conf:
|
tgraf_sync_escurl_conf:
|
||||||
@@ -61,22 +61,102 @@ tgraf_sync_escurl_conf:
|
|||||||
- source: salt://elasticsearch/curl.config
|
- source: salt://elasticsearch/curl.config
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
# so-container-stats runs on the host as somon, a docker group member, so the container does
|
||||||
|
# not need the docker socket
|
||||||
|
somongroup:
|
||||||
|
group.present:
|
||||||
|
- name: somon
|
||||||
|
- gid: 961
|
||||||
|
|
||||||
|
# cron chdirs to $HOME before running a job, so home must exist
|
||||||
|
somon:
|
||||||
|
user.present:
|
||||||
|
- uid: 961
|
||||||
|
- gid: 961
|
||||||
|
- home: /opt/so/log/somon
|
||||||
|
- createhome: False
|
||||||
|
- shell: /sbin/nologin
|
||||||
|
- groups:
|
||||||
|
- docker
|
||||||
|
# renumbering an existing somon is a no-op on a fresh host and lets a host created
|
||||||
|
# before the id changed converge instead of failing the whole telegraf state
|
||||||
|
- allow_uid_change: True
|
||||||
|
- allow_gid_change: True
|
||||||
|
- require:
|
||||||
|
- group: somongroup
|
||||||
|
|
||||||
|
somonlogdir:
|
||||||
|
file.directory:
|
||||||
|
- name: /opt/so/log/somon
|
||||||
|
- user: 961
|
||||||
|
- group: 961
|
||||||
|
- mode: 755
|
||||||
|
# the lock file is not otherwise managed; recurse so a renumber rechowns it too
|
||||||
|
- recurse:
|
||||||
|
- user
|
||||||
|
- group
|
||||||
|
- require:
|
||||||
|
- user: somon
|
||||||
|
|
||||||
|
containers_log:
|
||||||
|
file.managed:
|
||||||
|
- name: /opt/so/log/somon/containers.log
|
||||||
|
- user: 961
|
||||||
|
- group: 961
|
||||||
|
- mode: 644
|
||||||
|
- replace: False
|
||||||
|
- require:
|
||||||
|
- file: somonlogdir
|
||||||
|
|
||||||
|
# telegraf reads on the same minute boundary the collector runs, and docker stats takes
|
||||||
|
# seconds, so write aside and rename rather than truncating the file telegraf is reading.
|
||||||
|
# ; not && so a failed run replaces the file instead of leaving stale metrics behind.
|
||||||
|
# flock -n keeps a run that outlives its minute from racing the next one over the same tmp
|
||||||
|
# file; the skipped run leaves a stale containers.log, which containers.sh discards by age
|
||||||
|
so-container-stats_cron:
|
||||||
|
cron.present:
|
||||||
|
- name: "flock -n /opt/so/log/somon/containers.lock -c '/usr/sbin/so-container-stats > /opt/so/log/somon/containers.log.tmp 2>&1; mv -f /opt/so/log/somon/containers.log.tmp /opt/so/log/somon/containers.log'"
|
||||||
|
- identifier: so-container-stats_cron
|
||||||
|
- user: somon
|
||||||
|
- minute: '*/1'
|
||||||
|
- hour: '*'
|
||||||
|
- daymonth: '*'
|
||||||
|
- month: '*'
|
||||||
|
- dayweek: '*'
|
||||||
|
- require:
|
||||||
|
- user: somon
|
||||||
|
|
||||||
|
# salt.lasthighstate touches this at order 9001, after the container starts; pre-create it so
|
||||||
|
# docker does not create a directory at the bind mount source
|
||||||
|
lasthighstate_placeholder:
|
||||||
|
file.managed:
|
||||||
|
- name: /opt/so/log/salt/lasthighstate
|
||||||
|
- mode: 644
|
||||||
|
- replace: False
|
||||||
|
- makedirs: True
|
||||||
|
|
||||||
telegraf_sbin:
|
telegraf_sbin:
|
||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://telegraf/tools/sbin
|
- source: salt://telegraf/tools/sbin
|
||||||
- user: 939
|
- user: root
|
||||||
- group: 939
|
- group: root
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
|
|
||||||
#telegraf_sbin_jinja:
|
# so-container-stats needs the per-stat toggles, so it renders instead of copying
|
||||||
# file.recurse:
|
tgraf_sbin_jinja:
|
||||||
# - name: /usr/sbin
|
file.recurse:
|
||||||
# - source: salt://telegraf/tools/sbin_jinja
|
- name: /usr/sbin
|
||||||
# - user: 939
|
- source: salt://telegraf/tools/sbin_jinja
|
||||||
# - group: 939
|
- user: root
|
||||||
# - file_mode: 755
|
- group: root
|
||||||
# - template: jinja
|
- file_mode: 755
|
||||||
|
# the unit test lives beside the script; it must not ship or be rendered as jinja
|
||||||
|
- exclude_pat:
|
||||||
|
- "*_test.py"
|
||||||
|
- template: jinja
|
||||||
|
- defaults:
|
||||||
|
CONTAINER_STATS: {{ TELEGRAFMERGED.container_stats }}
|
||||||
|
|
||||||
tgrafconf:
|
tgrafconf:
|
||||||
file.managed:
|
file.managed:
|
||||||
|
|||||||
@@ -10,6 +10,69 @@ telegraf:
|
|||||||
flush_jitter: '0s'
|
flush_jitter: '0s'
|
||||||
debug: false
|
debug: false
|
||||||
quiet: false
|
quiet: false
|
||||||
|
container_stats:
|
||||||
|
tags:
|
||||||
|
identity: False
|
||||||
|
engine:
|
||||||
|
n_containers: False
|
||||||
|
n_containers_running: False
|
||||||
|
n_containers_stopped: False
|
||||||
|
n_containers_paused: False
|
||||||
|
n_images: False
|
||||||
|
n_cpus: False
|
||||||
|
n_goroutines: False
|
||||||
|
n_used_file_descriptors: False
|
||||||
|
n_listener_events: False
|
||||||
|
memory_total: False
|
||||||
|
cpu:
|
||||||
|
usage_percent: True
|
||||||
|
usage_total: False
|
||||||
|
usage_in_usermode: False
|
||||||
|
usage_in_kernelmode: False
|
||||||
|
usage_system: False
|
||||||
|
throttling_periods: False
|
||||||
|
throttling_throttled_periods: False
|
||||||
|
throttling_throttled_time: False
|
||||||
|
container_id: False
|
||||||
|
mem:
|
||||||
|
usage_percent: True
|
||||||
|
usage: False
|
||||||
|
limit: False
|
||||||
|
max_usage: False
|
||||||
|
active_anon: False
|
||||||
|
active_file: False
|
||||||
|
inactive_anon: False
|
||||||
|
inactive_file: False
|
||||||
|
unevictable: False
|
||||||
|
pgfault: False
|
||||||
|
pgmajfault: False
|
||||||
|
container_id: False
|
||||||
|
net:
|
||||||
|
rx_bytes: True
|
||||||
|
rx_packets: False
|
||||||
|
rx_errors: False
|
||||||
|
rx_dropped: False
|
||||||
|
tx_bytes: False
|
||||||
|
tx_packets: False
|
||||||
|
tx_errors: False
|
||||||
|
tx_dropped: False
|
||||||
|
container_id: False
|
||||||
|
blkio:
|
||||||
|
io_service_bytes_recursive_read: False
|
||||||
|
io_service_bytes_recursive_write: False
|
||||||
|
container_id: False
|
||||||
|
status:
|
||||||
|
uptime_ns: True
|
||||||
|
oomkilled: True
|
||||||
|
pid: False
|
||||||
|
exitcode: False
|
||||||
|
restart_count: False
|
||||||
|
started_at: False
|
||||||
|
finished_at: False
|
||||||
|
container_id: False
|
||||||
|
health:
|
||||||
|
health_status: False
|
||||||
|
failing_streak: False
|
||||||
scripts:
|
scripts:
|
||||||
eval:
|
eval:
|
||||||
- agentstatus.sh
|
- agentstatus.sh
|
||||||
@@ -19,6 +82,7 @@ telegraf:
|
|||||||
- oldpcap.sh
|
- oldpcap.sh
|
||||||
- os.sh
|
- os.sh
|
||||||
- raid.sh
|
- raid.sh
|
||||||
|
- containers.sh
|
||||||
- sostatus.sh
|
- sostatus.sh
|
||||||
- suriloss.sh
|
- suriloss.sh
|
||||||
- surirules.sh
|
- surirules.sh
|
||||||
@@ -34,6 +98,7 @@ telegraf:
|
|||||||
- os.sh
|
- os.sh
|
||||||
- raid.sh
|
- raid.sh
|
||||||
- redis.sh
|
- redis.sh
|
||||||
|
- containers.sh
|
||||||
- sostatus.sh
|
- sostatus.sh
|
||||||
- suriloss.sh
|
- suriloss.sh
|
||||||
- surirules.sh
|
- surirules.sh
|
||||||
@@ -47,6 +112,7 @@ telegraf:
|
|||||||
- os.sh
|
- os.sh
|
||||||
- raid.sh
|
- raid.sh
|
||||||
- redis.sh
|
- redis.sh
|
||||||
|
- containers.sh
|
||||||
- sostatus.sh
|
- sostatus.sh
|
||||||
- features.sh
|
- features.sh
|
||||||
managerhype:
|
managerhype:
|
||||||
@@ -56,6 +122,7 @@ telegraf:
|
|||||||
- os.sh
|
- os.sh
|
||||||
- raid.sh
|
- raid.sh
|
||||||
- redis.sh
|
- redis.sh
|
||||||
|
- containers.sh
|
||||||
- sostatus.sh
|
- sostatus.sh
|
||||||
- features.sh
|
- features.sh
|
||||||
managersearch:
|
managersearch:
|
||||||
@@ -66,12 +133,14 @@ telegraf:
|
|||||||
- os.sh
|
- os.sh
|
||||||
- raid.sh
|
- raid.sh
|
||||||
- redis.sh
|
- redis.sh
|
||||||
|
- containers.sh
|
||||||
- sostatus.sh
|
- sostatus.sh
|
||||||
- features.sh
|
- features.sh
|
||||||
import:
|
import:
|
||||||
- influxdbsize.sh
|
- influxdbsize.sh
|
||||||
- lasthighstate.sh
|
- lasthighstate.sh
|
||||||
- os.sh
|
- os.sh
|
||||||
|
- containers.sh
|
||||||
- sostatus.sh
|
- sostatus.sh
|
||||||
sensor:
|
sensor:
|
||||||
- checkfiles.sh
|
- checkfiles.sh
|
||||||
@@ -79,6 +148,7 @@ telegraf:
|
|||||||
- oldpcap.sh
|
- oldpcap.sh
|
||||||
- os.sh
|
- os.sh
|
||||||
- raid.sh
|
- raid.sh
|
||||||
|
- containers.sh
|
||||||
- sostatus.sh
|
- sostatus.sh
|
||||||
- suriloss.sh
|
- suriloss.sh
|
||||||
- surirules.sh
|
- surirules.sh
|
||||||
@@ -93,6 +163,7 @@ telegraf:
|
|||||||
- os.sh
|
- os.sh
|
||||||
- raid.sh
|
- raid.sh
|
||||||
- redis.sh
|
- redis.sh
|
||||||
|
- containers.sh
|
||||||
- sostatus.sh
|
- sostatus.sh
|
||||||
- suriloss.sh
|
- suriloss.sh
|
||||||
- surirules.sh
|
- surirules.sh
|
||||||
@@ -101,12 +172,14 @@ telegraf:
|
|||||||
idh:
|
idh:
|
||||||
- lasthighstate.sh
|
- lasthighstate.sh
|
||||||
- os.sh
|
- os.sh
|
||||||
|
- containers.sh
|
||||||
- sostatus.sh
|
- sostatus.sh
|
||||||
searchnode:
|
searchnode:
|
||||||
- eps.sh
|
- eps.sh
|
||||||
- lasthighstate.sh
|
- lasthighstate.sh
|
||||||
- os.sh
|
- os.sh
|
||||||
- raid.sh
|
- raid.sh
|
||||||
|
- containers.sh
|
||||||
- sostatus.sh
|
- sostatus.sh
|
||||||
- features.sh
|
- features.sh
|
||||||
receiver:
|
receiver:
|
||||||
@@ -115,16 +188,20 @@ telegraf:
|
|||||||
- os.sh
|
- os.sh
|
||||||
- raid.sh
|
- raid.sh
|
||||||
- redis.sh
|
- redis.sh
|
||||||
|
- containers.sh
|
||||||
- sostatus.sh
|
- sostatus.sh
|
||||||
fleet:
|
fleet:
|
||||||
- lasthighstate.sh
|
- lasthighstate.sh
|
||||||
- os.sh
|
- os.sh
|
||||||
|
- containers.sh
|
||||||
- sostatus.sh
|
- sostatus.sh
|
||||||
hypervisor:
|
hypervisor:
|
||||||
- lasthighstate.sh
|
- lasthighstate.sh
|
||||||
- os.sh
|
- os.sh
|
||||||
|
- containers.sh
|
||||||
- sostatus.sh
|
- sostatus.sh
|
||||||
desktop:
|
desktop:
|
||||||
- lasthighstate.sh
|
- lasthighstate.sh
|
||||||
- os.sh
|
- os.sh
|
||||||
|
- containers.sh
|
||||||
- sostatus.sh
|
- sostatus.sh
|
||||||
@@ -13,6 +13,11 @@ so-telegraf:
|
|||||||
docker_container.absent:
|
docker_container.absent:
|
||||||
- force: True
|
- force: True
|
||||||
|
|
||||||
|
so-container-stats_cron:
|
||||||
|
cron.absent:
|
||||||
|
- identifier: so-container-stats_cron
|
||||||
|
- user: somon
|
||||||
|
|
||||||
so-telegraf_so-status.disabled:
|
so-telegraf_so-status.disabled:
|
||||||
file.comment:
|
file.comment:
|
||||||
- name: /opt/so/conf/so-status/so-status.conf
|
- name: /opt/so/conf/so-status/so-status.conf
|
||||||
|
|||||||
Loaded 100 of 117 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user