mirror of
https://github.com/Security-Onion-Solutions/securityonion.git
synced 2026-02-28 09:55:32 +01:00
Compare commits
84 Commits
jertel/wip
...
idstools-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2284283b17 | ||
|
|
55e984df4c | ||
|
|
5e7b0cfe0e | ||
|
|
ee4a2f00be | ||
|
|
c4b6cef8ee | ||
|
|
12b3081a62 | ||
|
|
91ea0e6952 | ||
|
|
0bcfec3f56 | ||
|
|
4d5ace2a89 | ||
|
|
f4be73fdde | ||
|
|
742649a337 | ||
|
|
32a26559dd | ||
|
|
7e5daf7f7f | ||
|
|
2552a5c17d | ||
|
|
fa479c4b89 | ||
|
|
479e3e0afa | ||
|
|
be35b59b8c | ||
|
|
c52d3269d6 | ||
|
|
3583b92836 | ||
|
|
2375061cfa | ||
|
|
1a9a087af2 | ||
|
|
bf16de7bfd | ||
|
|
863c7abc8b | ||
|
|
7170289a5e | ||
|
|
ca040044bb | ||
|
|
f17e2961ed | ||
|
|
bbc7668786 | ||
|
|
1888f9e757 | ||
|
|
5822d1c974 | ||
|
|
b3139c5008 | ||
|
|
0a64bb0a87 | ||
|
|
cf6b5aeceb | ||
|
|
bcb850d98a | ||
|
|
f0139c04f0 | ||
|
|
78ae6cd84c | ||
|
|
b7e0b2faa3 | ||
|
|
bfd1cf2d9b | ||
|
|
8cc8a63a4e | ||
|
|
b3a0eb0761 | ||
|
|
38e45056f2 | ||
|
|
39bad077ae | ||
|
|
b349d27e8c | ||
|
|
90eee49ab6 | ||
|
|
f025886b31 | ||
|
|
7fa01f5fd5 | ||
|
|
75e1f74244 | ||
|
|
4036469857 | ||
|
|
256c1122c3 | ||
|
|
aa2a1a3d3c | ||
|
|
93f52453b4 | ||
|
|
a9307aa308 | ||
|
|
0ebd8e4d6c | ||
|
|
8fc3011f92 | ||
|
|
911c9d56db | ||
|
|
c1273c3d2c | ||
|
|
d0018c9333 | ||
|
|
3349c1a936 | ||
|
|
32819c8635 | ||
|
|
58c0a9183c | ||
|
|
7dfd212519 | ||
|
|
b8fb0fa735 | ||
|
|
e6f767b613 | ||
|
|
d00fb4ccf7 | ||
|
|
a29eff37a0 | ||
|
|
534a0ad41f | ||
|
|
4c86275cd6 | ||
|
|
a1c806a944 | ||
|
|
3d1a2c12ec | ||
|
|
8538e5572e | ||
|
|
9b525612a8 | ||
|
|
fb364aec5d | ||
|
|
ed014b431e | ||
|
|
82ca64d66f | ||
|
|
7e0fb73fec | ||
|
|
c28bcfa85e | ||
|
|
be6d94d65b | ||
|
|
ada463320b | ||
|
|
2b05583035 | ||
|
|
4d6b2de374 | ||
|
|
41d94b6bfd | ||
|
|
2d74002e9e | ||
|
|
e7e379ce82 | ||
|
|
ff8790b35b | ||
|
|
c6168c1487 |
@@ -1,10 +1,12 @@
|
||||
{% macro remove_comments(bpfmerged, app) %}
|
||||
|
||||
{# remove comments from the bpf #}
|
||||
{% set app_list = [] %}
|
||||
{% for bpf in bpfmerged[app] %}
|
||||
{% if bpf.strip().startswith('#') %}
|
||||
{% do bpfmerged[app].pop(loop.index0) %}
|
||||
{% if not bpf.strip().startswith('#') %}
|
||||
{% do app_list.append(bpf) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% do bpfmerged.update({app: app_list}) %}
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
{% endif %}
|
||||
|
||||
{% if PCAPBPF %}
|
||||
{% set PCAP_BPF_CALC = salt['cmd.run_all']('/usr/sbin/so-bpf-compile ' ~ GLOBALS.sensor.interface ~ ' ' ~ PCAPBPF|join(" "), cwd='/root') %}
|
||||
{% set PCAP_BPF_CALC = salt['cmd.script']('salt://common/tools/sbin/so-bpf-compile', GLOBALS.sensor.interface + ' ' + PCAPBPF|join(" "),cwd='/root') %}
|
||||
{% if PCAP_BPF_CALC['retcode'] == 0 %}
|
||||
{% set PCAP_BPF_STATUS = 1 %}
|
||||
{% set STENO_BPF_COMPILED = ",\\\"--filter=" + PCAP_BPF_CALC['stdout'] + "\\\"" %}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{% set SURICATABPF = BPFMERGED.suricata %}
|
||||
|
||||
{% if SURICATABPF %}
|
||||
{% set SURICATA_BPF_CALC = salt['cmd.run_all']('/usr/sbin/so-bpf-compile ' ~ GLOBALS.sensor.interface ~ ' ' ~ SURICATABPF|join(" "), cwd='/root') %}
|
||||
{% set SURICATA_BPF_CALC = salt['cmd.script']('salt://common/tools/sbin/so-bpf-compile', GLOBALS.sensor.interface + ' ' + SURICATABPF|join(" "),cwd='/root') %}
|
||||
{% if SURICATA_BPF_CALC['retcode'] == 0 %}
|
||||
{% set SURICATA_BPF_STATUS = 1 %}
|
||||
{% endif %}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{% set ZEEKBPF = BPFMERGED.zeek %}
|
||||
|
||||
{% if ZEEKBPF %}
|
||||
{% set ZEEK_BPF_CALC = salt['cmd.run_all']('/usr/sbin/so-bpf-compile ' ~ GLOBALS.sensor.interface ~ ' ' ~ ZEEKBPF|join(" "), cwd='/root') %}
|
||||
{% set ZEEK_BPF_CALC = salt['cmd.script']('salt://common/tools/sbin/so-bpf-compile', GLOBALS.sensor.interface + ' ' + ZEEKBPF|join(" "),cwd='/root') %}
|
||||
{% if ZEEK_BPF_CALC['retcode'] == 0 %}
|
||||
{% set ZEEK_BPF_STATUS = 1 %}
|
||||
{% endif %}
|
||||
|
||||
@@ -228,6 +228,7 @@ if [[ $EXCLUDE_KNOWN_ERRORS == 'Y' ]]; then
|
||||
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|TransformTask\] \[logs-(tychon|aws_billing|microsoft_defender_endpoint).*user so_kibana lacks the required permissions \[logs-\1" # Known issue with 3 integrations using kibana_system role vs creating unique api creds with proper permissions.
|
||||
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|manifest unknown" # appears in so-dockerregistry log for so-tcpreplay following docker upgrade to 29.2.1-1
|
||||
fi
|
||||
|
||||
RESULT=0
|
||||
|
||||
@@ -20,20 +20,20 @@ dockergroup:
|
||||
dockerheldpackages:
|
||||
pkg.installed:
|
||||
- pkgs:
|
||||
- containerd.io: 1.7.21-1
|
||||
- docker-ce: 5:27.2.0-1~debian.12~bookworm
|
||||
- docker-ce-cli: 5:27.2.0-1~debian.12~bookworm
|
||||
- docker-ce-rootless-extras: 5:27.2.0-1~debian.12~bookworm
|
||||
- containerd.io: 2.2.1-1~debian.12~bookworm
|
||||
- docker-ce: 5:29.2.1-1~debian.12~bookworm
|
||||
- docker-ce-cli: 5:29.2.1-1~debian.12~bookworm
|
||||
- docker-ce-rootless-extras: 5:29.2.1-1~debian.12~bookworm
|
||||
- hold: True
|
||||
- update_holds: True
|
||||
{% elif grains.oscodename == 'jammy' %}
|
||||
dockerheldpackages:
|
||||
pkg.installed:
|
||||
- pkgs:
|
||||
- containerd.io: 1.7.21-1
|
||||
- docker-ce: 5:27.2.0-1~ubuntu.22.04~jammy
|
||||
- docker-ce-cli: 5:27.2.0-1~ubuntu.22.04~jammy
|
||||
- docker-ce-rootless-extras: 5:27.2.0-1~ubuntu.22.04~jammy
|
||||
- containerd.io: 2.2.1-1~ubuntu.22.04~jammy
|
||||
- docker-ce: 5:29.2.1-1~ubuntu.22.04~jammy
|
||||
- docker-ce-cli: 5:29.2.1-1~ubuntu.22.04~jammy
|
||||
- docker-ce-rootless-extras: 5:29.2.1-1~ubuntu.22.04~jammy
|
||||
- hold: True
|
||||
- update_holds: True
|
||||
{% else %}
|
||||
@@ -51,10 +51,10 @@ dockerheldpackages:
|
||||
dockerheldpackages:
|
||||
pkg.installed:
|
||||
- pkgs:
|
||||
- containerd.io: 1.7.21-3.1.el9
|
||||
- docker-ce: 3:27.2.0-1.el9
|
||||
- docker-ce-cli: 1:27.2.0-1.el9
|
||||
- docker-ce-rootless-extras: 27.2.0-1.el9
|
||||
- containerd.io: 2.2.1-1.el9
|
||||
- docker-ce: 3:29.2.1-1.el9
|
||||
- docker-ce-cli: 1:29.2.1-1.el9
|
||||
- docker-ce-rootless-extras: 29.2.1-1.el9
|
||||
- hold: True
|
||||
- update_holds: True
|
||||
{% endif %}
|
||||
@@ -117,4 +117,4 @@ sos_docker_net:
|
||||
com.docker.network.bridge.enable_ip_masquerade: 'true'
|
||||
com.docker.network.bridge.enable_icc: 'true'
|
||||
com.docker.network.bridge.host_binding_ipv4: '0.0.0.0'
|
||||
- unless: 'docker network ls | grep sobridge'
|
||||
- unless: ip l | grep sobridge
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"paths": [
|
||||
"/nsm/suricata/eve*.json"
|
||||
],
|
||||
"data_stream.dataset": "filestream.generic",
|
||||
"data_stream.dataset": "suricata",
|
||||
"pipeline": "suricata.common",
|
||||
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
||||
"exclude_files": [
|
||||
@@ -41,4 +41,4 @@
|
||||
}
|
||||
},
|
||||
"force": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -858,6 +858,8 @@ elasticsearch:
|
||||
composed_of:
|
||||
- agent-mappings
|
||||
- dtc-agent-mappings
|
||||
- event-mappings
|
||||
- file-mappings
|
||||
- host-mappings
|
||||
- dtc-host-mappings
|
||||
- http-mappings
|
||||
|
||||
@@ -81,6 +81,14 @@
|
||||
"ignore_missing": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"rename": {
|
||||
"field": "file",
|
||||
"target_field": "file.path",
|
||||
"ignore_failure": true,
|
||||
"ignore_missing": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"pipeline": {
|
||||
"name": "common"
|
||||
|
||||
@@ -84,13 +84,6 @@ elasticsearch:
|
||||
custom008: *pipelines
|
||||
custom009: *pipelines
|
||||
custom010: *pipelines
|
||||
managed_integrations:
|
||||
description: List of integrations to add into SOC config UI. Enter the full or partial integration name. Eg. 1password, 1pass
|
||||
forcedType: "[]string"
|
||||
multiline: True
|
||||
global: True
|
||||
advanced: True
|
||||
helpLink: elasticsearch.html
|
||||
index_settings:
|
||||
global_overrides:
|
||||
index_template:
|
||||
|
||||
@@ -1,91 +1,103 @@
|
||||
{
|
||||
"_meta": {
|
||||
"documentation": "https://www.elastic.co/guide/en/ecs/current/ecs-dns.html",
|
||||
"ecs_version": "1.12.2"
|
||||
},
|
||||
"template": {
|
||||
"mappings": {
|
||||
"properties": {
|
||||
"dns": {
|
||||
"properties": {
|
||||
"answers": {
|
||||
"properties": {
|
||||
"class": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"data": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"name": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"ttl": {
|
||||
"type": "long"
|
||||
},
|
||||
"type": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
"_meta": {
|
||||
"documentation": "https://www.elastic.co/guide/en/ecs/current/ecs-dns.html",
|
||||
"ecs_version": "1.12.2"
|
||||
},
|
||||
"template": {
|
||||
"mappings": {
|
||||
"properties": {
|
||||
"dns": {
|
||||
"properties": {
|
||||
"answers": {
|
||||
"properties": {
|
||||
"class": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"data": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"name": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"ttl": {
|
||||
"type": "long"
|
||||
},
|
||||
"type": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"header_flags": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"id": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"op_code": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"question": {
|
||||
"properties": {
|
||||
"class": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"name": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"registered_domain": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"subdomain": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"top_level_domain": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"type": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
},
|
||||
"query": {
|
||||
"properties" :{
|
||||
"type":{
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"type_name": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
},
|
||||
"resolved_ip": {
|
||||
"type": "ip"
|
||||
},
|
||||
"response_code": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"type": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"header_flags": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"id": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"op_code": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"question": {
|
||||
"properties": {
|
||||
"class": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"name": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"registered_domain": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"subdomain": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"top_level_domain": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"type": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
},
|
||||
"resolved_ip": {
|
||||
"type": "ip"
|
||||
},
|
||||
"response_code": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"type": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,13 @@
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"bytes": {
|
||||
"properties": {
|
||||
"missing": {
|
||||
"type": "long"
|
||||
}
|
||||
}
|
||||
},
|
||||
"code_signature": {
|
||||
"properties": {
|
||||
"digest_algorithm": {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# Elastic License 2.0.
|
||||
|
||||
{# Managed elasticsearch/soc_elasticsearch.yaml file for adding integration configuration items to UI #}
|
||||
{% set managed_integrations = salt['pillar.get']('elasticsearch:managed_integrations', []) %}
|
||||
{% set managed_integrations = salt['pillar.get']('manager:managed_integrations', []) %}
|
||||
{% if managed_integrations and salt['file.file_exists']('/opt/so/state/esfleet_package_components.json') and salt['file.file_exists']('/opt/so/state/esfleet_component_templates.json') %}
|
||||
{% from 'elasticfleet/integration-defaults.map.jinja' import ADDON_INTEGRATION_DEFAULTS %}
|
||||
{% set addon_integration_keys = ADDON_INTEGRATION_DEFAULTS.keys() %}
|
||||
|
||||
@@ -78,3 +78,10 @@ manager:
|
||||
advanced: True
|
||||
helpLink: elastic-fleet.html
|
||||
forcedType: int
|
||||
managed_integrations:
|
||||
description: List of integrations to add into SOC config UI. Enter the full or partial integration name. Eg. 1password, 1pass
|
||||
forcedType: "[]string"
|
||||
multiline: True
|
||||
global: True
|
||||
advanced: True
|
||||
helpLink: elasticsearch.html
|
||||
@@ -9,6 +9,7 @@ import os
|
||||
import sys
|
||||
import time
|
||||
import yaml
|
||||
import json
|
||||
|
||||
lockFile = "/tmp/so-yaml.lock"
|
||||
|
||||
@@ -16,19 +17,24 @@ lockFile = "/tmp/so-yaml.lock"
|
||||
def showUsage(args):
|
||||
print('Usage: {} <COMMAND> <YAML_FILE> [ARGS...]'.format(sys.argv[0]), file=sys.stderr)
|
||||
print(' General commands:', file=sys.stderr)
|
||||
print(' append - Append a list item to a yaml key, if it exists and is a list. Requires KEY and LISTITEM args.', file=sys.stderr)
|
||||
print(' removelistitem - Remove a list item from a yaml key, if it exists and is a list. Requires KEY and LISTITEM args.', file=sys.stderr)
|
||||
print(' add - Add a new key and set its value. Fails if key already exists. Requires KEY and VALUE args.', file=sys.stderr)
|
||||
print(' get - Displays (to stdout) the value stored in the given key. Requires KEY arg.', file=sys.stderr)
|
||||
print(' remove - Removes a yaml key, if it exists. Requires KEY arg.', file=sys.stderr)
|
||||
print(' replace - Replaces (or adds) a new key and set its value. Requires KEY and VALUE args.', file=sys.stderr)
|
||||
print(' help - Prints this usage information.', file=sys.stderr)
|
||||
print(' append - Append a list item to a yaml key, if it exists and is a list. Requires KEY and LISTITEM args.', file=sys.stderr)
|
||||
print(' appendlistobject - Append an object to a yaml list key. Requires KEY and JSON_OBJECT args.', file=sys.stderr)
|
||||
print(' removelistitem - Remove a list item from a yaml key, if it exists and is a list. Requires KEY and LISTITEM args.', file=sys.stderr)
|
||||
print(' replacelistobject - Replace a list object based on a condition. Requires KEY, CONDITION_FIELD, CONDITION_VALUE, and JSON_OBJECT args.', file=sys.stderr)
|
||||
print(' add - Add a new key and set its value. Fails if key already exists. Requires KEY and VALUE args.', file=sys.stderr)
|
||||
print(' get - Displays (to stdout) the value stored in the given key. Requires KEY arg.', file=sys.stderr)
|
||||
print(' remove - Removes a yaml key, if it exists. Requires KEY arg.', file=sys.stderr)
|
||||
print(' replace - Replaces (or adds) a new key and set its value. Requires KEY and VALUE args.', file=sys.stderr)
|
||||
print(' help - Prints this usage information.', file=sys.stderr)
|
||||
print('', file=sys.stderr)
|
||||
print(' Where:', file=sys.stderr)
|
||||
print(' YAML_FILE - Path to the file that will be modified. Ex: /opt/so/conf/service/conf.yaml', file=sys.stderr)
|
||||
print(' KEY - YAML key, does not support \' or " characters at this time. Ex: level1.level2', file=sys.stderr)
|
||||
print(' VALUE - Value to set for a given key. Can be a literal value or file:<path> to load from a YAML file.', file=sys.stderr)
|
||||
print(' LISTITEM - Item to append to a given key\'s list value. Can be a literal value or file:<path> to load from a YAML file.', file=sys.stderr)
|
||||
print(' YAML_FILE - Path to the file that will be modified. Ex: /opt/so/conf/service/conf.yaml', file=sys.stderr)
|
||||
print(' KEY - YAML key, does not support \' or " characters at this time. Ex: level1.level2', file=sys.stderr)
|
||||
print(' VALUE - Value to set for a given key. Can be a literal value or file:<path> to load from a YAML file.', file=sys.stderr)
|
||||
print(' LISTITEM - Item to append to a given key\'s list value. Can be a literal value or file:<path> to load from a YAML file.', file=sys.stderr)
|
||||
print(' JSON_OBJECT - JSON string representing an object to append to a list.', file=sys.stderr)
|
||||
print(' CONDITION_FIELD - Field name to match in list items (e.g., "name").', file=sys.stderr)
|
||||
print(' CONDITION_VALUE - Value to match for the condition field.', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@@ -122,6 +128,52 @@ def append(args):
|
||||
return 0
|
||||
|
||||
|
||||
def appendListObjectItem(content, key, listObject):
|
||||
pieces = key.split(".", 1)
|
||||
if len(pieces) > 1:
|
||||
appendListObjectItem(content[pieces[0]], pieces[1], listObject)
|
||||
else:
|
||||
try:
|
||||
if not isinstance(content[key], list):
|
||||
raise AttributeError("Value is not a list")
|
||||
content[key].append(listObject)
|
||||
except AttributeError:
|
||||
print("The existing value for the given key is not a list. No action was taken on the file.", file=sys.stderr)
|
||||
return 1
|
||||
except KeyError:
|
||||
print("The key provided does not exist. No action was taken on the file.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
def appendlistobject(args):
|
||||
if len(args) != 3:
|
||||
print('Missing filename, key arg, or JSON object to append', file=sys.stderr)
|
||||
showUsage(None)
|
||||
return 1
|
||||
|
||||
filename = args[0]
|
||||
key = args[1]
|
||||
jsonString = args[2]
|
||||
|
||||
try:
|
||||
# Parse the JSON string into a Python dictionary
|
||||
listObject = json.loads(jsonString)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f'Invalid JSON string: {e}', file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# Verify that the parsed content is a dictionary (object)
|
||||
if not isinstance(listObject, dict):
|
||||
print('The JSON string must represent an object (dictionary), not an array or primitive value.', file=sys.stderr)
|
||||
return 1
|
||||
|
||||
content = loadYaml(filename)
|
||||
appendListObjectItem(content, key, listObject)
|
||||
writeYaml(filename, content)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def removelistitem(args):
|
||||
if len(args) != 3:
|
||||
print('Missing filename, key arg, or list item to remove', file=sys.stderr)
|
||||
@@ -139,6 +191,68 @@ def removelistitem(args):
|
||||
return 0
|
||||
|
||||
|
||||
def replaceListObjectByCondition(content, key, conditionField, conditionValue, newObject):
|
||||
pieces = key.split(".", 1)
|
||||
if len(pieces) > 1:
|
||||
replaceListObjectByCondition(content[pieces[0]], pieces[1], conditionField, conditionValue, newObject)
|
||||
else:
|
||||
try:
|
||||
if not isinstance(content[key], list):
|
||||
raise AttributeError("Value is not a list")
|
||||
|
||||
# Find and replace the item that matches the condition
|
||||
found = False
|
||||
for i, item in enumerate(content[key]):
|
||||
if isinstance(item, dict) and item.get(conditionField) == conditionValue:
|
||||
content[key][i] = newObject
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
print(f"No list item found with {conditionField}={conditionValue}. No action was taken on the file.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
except AttributeError:
|
||||
print("The existing value for the given key is not a list. No action was taken on the file.", file=sys.stderr)
|
||||
return 1
|
||||
except KeyError:
|
||||
print("The key provided does not exist. No action was taken on the file.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
def replacelistobject(args):
|
||||
if len(args) != 5:
|
||||
print('Missing filename, key arg, condition field, condition value, or JSON object', file=sys.stderr)
|
||||
showUsage(None)
|
||||
return 1
|
||||
|
||||
filename = args[0]
|
||||
key = args[1]
|
||||
conditionField = args[2]
|
||||
conditionValue = args[3]
|
||||
jsonString = args[4]
|
||||
|
||||
try:
|
||||
# Parse the JSON string into a Python dictionary
|
||||
newObject = json.loads(jsonString)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f'Invalid JSON string: {e}', file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# Verify that the parsed content is a dictionary (object)
|
||||
if not isinstance(newObject, dict):
|
||||
print('The JSON string must represent an object (dictionary), not an array or primitive value.', file=sys.stderr)
|
||||
return 1
|
||||
|
||||
content = loadYaml(filename)
|
||||
result = replaceListObjectByCondition(content, key, conditionField, conditionValue, newObject)
|
||||
|
||||
if result != 1:
|
||||
writeYaml(filename, content)
|
||||
|
||||
return result if result is not None else 0
|
||||
|
||||
|
||||
def addKey(content, key, value):
|
||||
pieces = key.split(".", 1)
|
||||
if len(pieces) > 1:
|
||||
@@ -229,7 +343,7 @@ def get(args):
|
||||
content = loadYaml(filename)
|
||||
output = getKeyValue(content, key)
|
||||
if output is None:
|
||||
print("Not found", file=sys.stderr)
|
||||
print(f"Key '{key}' not found by so-yaml.py", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
print(yaml.safe_dump(output))
|
||||
@@ -247,7 +361,9 @@ def main():
|
||||
"help": showUsage,
|
||||
"add": add,
|
||||
"append": append,
|
||||
"appendlistobject": appendlistobject,
|
||||
"removelistitem": removelistitem,
|
||||
"replacelistobject": replacelistobject,
|
||||
"get": get,
|
||||
"remove": remove,
|
||||
"replace": replace,
|
||||
|
||||
@@ -580,3 +580,340 @@ class TestRemoveListItem(unittest.TestCase):
|
||||
soyaml.main()
|
||||
sysmock.assert_called()
|
||||
self.assertEqual("The existing value for the given key is not a list. No action was taken on the file.\n", mock_stderr.getvalue())
|
||||
|
||||
|
||||
class TestAppendListObject(unittest.TestCase):
|
||||
|
||||
def test_appendlistobject_missing_arg(self):
|
||||
with patch('sys.exit', new=MagicMock()) as sysmock:
|
||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||
sys.argv = ["cmd", "help"]
|
||||
soyaml.appendlistobject(["file", "key"])
|
||||
sysmock.assert_called()
|
||||
self.assertIn("Missing filename, key arg, or JSON object to append", mock_stderr.getvalue())
|
||||
|
||||
def test_appendlistobject(self):
|
||||
filename = "/tmp/so-yaml_test-appendlistobject.yaml"
|
||||
file = open(filename, "w")
|
||||
file.write("{key1: { child1: 123 }, key2: [{name: item1, value: 10}]}")
|
||||
file.close()
|
||||
|
||||
json_obj = '{"name": "item2", "value": 20}'
|
||||
soyaml.appendlistobject([filename, "key2", json_obj])
|
||||
|
||||
file = open(filename, "r")
|
||||
actual = file.read()
|
||||
file.close()
|
||||
|
||||
expected = "key1:\n child1: 123\nkey2:\n- name: item1\n value: 10\n- name: item2\n value: 20\n"
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_appendlistobject_nested(self):
|
||||
filename = "/tmp/so-yaml_test-appendlistobject.yaml"
|
||||
file = open(filename, "w")
|
||||
file.write("{key1: { child1: [{name: a, id: 1}], child2: abc }, key2: false}")
|
||||
file.close()
|
||||
|
||||
json_obj = '{"name": "b", "id": 2}'
|
||||
soyaml.appendlistobject([filename, "key1.child1", json_obj])
|
||||
|
||||
file = open(filename, "r")
|
||||
actual = file.read()
|
||||
file.close()
|
||||
|
||||
# YAML doesn't guarantee key order in dictionaries, so check for content
|
||||
self.assertIn("child1:", actual)
|
||||
self.assertIn("name: a", actual)
|
||||
self.assertIn("id: 1", actual)
|
||||
self.assertIn("name: b", actual)
|
||||
self.assertIn("id: 2", actual)
|
||||
self.assertIn("child2: abc", actual)
|
||||
self.assertIn("key2: false", actual)
|
||||
|
||||
def test_appendlistobject_nested_deep(self):
|
||||
filename = "/tmp/so-yaml_test-appendlistobject.yaml"
|
||||
file = open(filename, "w")
|
||||
file.write("{key1: { child1: 123, child2: { deep1: 45, deep2: [{x: 1}] } }, key2: false}")
|
||||
file.close()
|
||||
|
||||
json_obj = '{"x": 2, "y": 3}'
|
||||
soyaml.appendlistobject([filename, "key1.child2.deep2", json_obj])
|
||||
|
||||
file = open(filename, "r")
|
||||
actual = file.read()
|
||||
file.close()
|
||||
|
||||
expected = "key1:\n child1: 123\n child2:\n deep1: 45\n deep2:\n - x: 1\n - x: 2\n y: 3\nkey2: false\n"
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_appendlistobject_invalid_json(self):
|
||||
filename = "/tmp/so-yaml_test-appendlistobject.yaml"
|
||||
file = open(filename, "w")
|
||||
file.write("{key1: [{name: item1}]}")
|
||||
file.close()
|
||||
|
||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||
result = soyaml.appendlistobject([filename, "key1", "{invalid json"])
|
||||
self.assertEqual(result, 1)
|
||||
self.assertIn("Invalid JSON string:", mock_stderr.getvalue())
|
||||
|
||||
def test_appendlistobject_not_dict(self):
|
||||
filename = "/tmp/so-yaml_test-appendlistobject.yaml"
|
||||
file = open(filename, "w")
|
||||
file.write("{key1: [{name: item1}]}")
|
||||
file.close()
|
||||
|
||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||
# Try to append an array instead of an object
|
||||
result = soyaml.appendlistobject([filename, "key1", "[1, 2, 3]"])
|
||||
self.assertEqual(result, 1)
|
||||
self.assertIn("The JSON string must represent an object (dictionary)", mock_stderr.getvalue())
|
||||
|
||||
def test_appendlistobject_not_dict_primitive(self):
|
||||
filename = "/tmp/so-yaml_test-appendlistobject.yaml"
|
||||
file = open(filename, "w")
|
||||
file.write("{key1: [{name: item1}]}")
|
||||
file.close()
|
||||
|
||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||
# Try to append a primitive value
|
||||
result = soyaml.appendlistobject([filename, "key1", "123"])
|
||||
self.assertEqual(result, 1)
|
||||
self.assertIn("The JSON string must represent an object (dictionary)", mock_stderr.getvalue())
|
||||
|
||||
def test_appendlistobject_key_noexist(self):
|
||||
filename = "/tmp/so-yaml_test-appendlistobject.yaml"
|
||||
file = open(filename, "w")
|
||||
file.write("{key1: [{name: item1}]}")
|
||||
file.close()
|
||||
|
||||
with patch('sys.exit', new=MagicMock()) as sysmock:
|
||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||
sys.argv = ["cmd", "appendlistobject", filename, "key2", '{"name": "item2"}']
|
||||
soyaml.main()
|
||||
sysmock.assert_called()
|
||||
self.assertEqual("The key provided does not exist. No action was taken on the file.\n", mock_stderr.getvalue())
|
||||
|
||||
def test_appendlistobject_key_noexist_deep(self):
|
||||
filename = "/tmp/so-yaml_test-appendlistobject.yaml"
|
||||
file = open(filename, "w")
|
||||
file.write("{key1: { child1: [{name: a}] }}")
|
||||
file.close()
|
||||
|
||||
with patch('sys.exit', new=MagicMock()) as sysmock:
|
||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||
sys.argv = ["cmd", "appendlistobject", filename, "key1.child2", '{"name": "b"}']
|
||||
soyaml.main()
|
||||
sysmock.assert_called()
|
||||
self.assertEqual("The key provided does not exist. No action was taken on the file.\n", mock_stderr.getvalue())
|
||||
|
||||
def test_appendlistobject_key_nonlist(self):
|
||||
filename = "/tmp/so-yaml_test-appendlistobject.yaml"
|
||||
file = open(filename, "w")
|
||||
file.write("{key1: { child1: 123 }}")
|
||||
file.close()
|
||||
|
||||
with patch('sys.exit', new=MagicMock()) as sysmock:
|
||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||
sys.argv = ["cmd", "appendlistobject", filename, "key1", '{"name": "item"}']
|
||||
soyaml.main()
|
||||
sysmock.assert_called()
|
||||
self.assertEqual("The existing value for the given key is not a list. No action was taken on the file.\n", mock_stderr.getvalue())
|
||||
|
||||
def test_appendlistobject_key_nonlist_deep(self):
|
||||
filename = "/tmp/so-yaml_test-appendlistobject.yaml"
|
||||
file = open(filename, "w")
|
||||
file.write("{key1: { child1: 123, child2: { deep1: 45 } }}")
|
||||
file.close()
|
||||
|
||||
with patch('sys.exit', new=MagicMock()) as sysmock:
|
||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||
sys.argv = ["cmd", "appendlistobject", filename, "key1.child2.deep1", '{"name": "item"}']
|
||||
soyaml.main()
|
||||
sysmock.assert_called()
|
||||
self.assertEqual("The existing value for the given key is not a list. No action was taken on the file.\n", mock_stderr.getvalue())
|
||||
|
||||
|
||||
class TestReplaceListObject(unittest.TestCase):
|
||||
|
||||
def test_replacelistobject_missing_arg(self):
|
||||
with patch('sys.exit', new=MagicMock()) as sysmock:
|
||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||
sys.argv = ["cmd", "help"]
|
||||
soyaml.replacelistobject(["file", "key", "field"])
|
||||
sysmock.assert_called()
|
||||
self.assertIn("Missing filename, key arg, condition field, condition value, or JSON object", mock_stderr.getvalue())
|
||||
|
||||
def test_replacelistobject(self):
|
||||
filename = "/tmp/so-yaml_test-replacelistobject.yaml"
|
||||
file = open(filename, "w")
|
||||
file.write("{key1: [{name: item1, value: 10}, {name: item2, value: 20}]}")
|
||||
file.close()
|
||||
|
||||
json_obj = '{"name": "item2", "value": 25, "extra": "field"}'
|
||||
soyaml.replacelistobject([filename, "key1", "name", "item2", json_obj])
|
||||
|
||||
file = open(filename, "r")
|
||||
actual = file.read()
|
||||
file.close()
|
||||
|
||||
expected = "key1:\n- name: item1\n value: 10\n- extra: field\n name: item2\n value: 25\n"
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_replacelistobject_nested(self):
|
||||
filename = "/tmp/so-yaml_test-replacelistobject.yaml"
|
||||
file = open(filename, "w")
|
||||
file.write("{key1: { child1: [{id: '1', status: active}, {id: '2', status: inactive}] }}")
|
||||
file.close()
|
||||
|
||||
json_obj = '{"id": "2", "status": "active", "updated": true}'
|
||||
soyaml.replacelistobject([filename, "key1.child1", "id", "2", json_obj])
|
||||
|
||||
file = open(filename, "r")
|
||||
actual = file.read()
|
||||
file.close()
|
||||
|
||||
expected = "key1:\n child1:\n - id: '1'\n status: active\n - id: '2'\n status: active\n updated: true\n"
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_replacelistobject_nested_deep(self):
|
||||
filename = "/tmp/so-yaml_test-replacelistobject.yaml"
|
||||
file = open(filename, "w")
|
||||
file.write("{key1: { child1: 123, child2: { deep1: 45, deep2: [{name: a, val: 1}, {name: b, val: 2}] } }}")
|
||||
file.close()
|
||||
|
||||
json_obj = '{"name": "b", "val": 99}'
|
||||
soyaml.replacelistobject([filename, "key1.child2.deep2", "name", "b", json_obj])
|
||||
|
||||
file = open(filename, "r")
|
||||
actual = file.read()
|
||||
file.close()
|
||||
|
||||
expected = "key1:\n child1: 123\n child2:\n deep1: 45\n deep2:\n - name: a\n val: 1\n - name: b\n val: 99\n"
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_replacelistobject_invalid_json(self):
|
||||
filename = "/tmp/so-yaml_test-replacelistobject.yaml"
|
||||
file = open(filename, "w")
|
||||
file.write("{key1: [{name: item1}]}")
|
||||
file.close()
|
||||
|
||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||
result = soyaml.replacelistobject([filename, "key1", "name", "item1", "{invalid json"])
|
||||
self.assertEqual(result, 1)
|
||||
self.assertIn("Invalid JSON string:", mock_stderr.getvalue())
|
||||
|
||||
def test_replacelistobject_not_dict(self):
|
||||
filename = "/tmp/so-yaml_test-replacelistobject.yaml"
|
||||
file = open(filename, "w")
|
||||
file.write("{key1: [{name: item1}]}")
|
||||
file.close()
|
||||
|
||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||
result = soyaml.replacelistobject([filename, "key1", "name", "item1", "[1, 2, 3]"])
|
||||
self.assertEqual(result, 1)
|
||||
self.assertIn("The JSON string must represent an object (dictionary)", mock_stderr.getvalue())
|
||||
|
||||
def test_replacelistobject_condition_not_found(self):
|
||||
filename = "/tmp/so-yaml_test-replacelistobject.yaml"
|
||||
file = open(filename, "w")
|
||||
file.write("{key1: [{name: item1, value: 10}, {name: item2, value: 20}]}")
|
||||
file.close()
|
||||
|
||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||
json_obj = '{"name": "item3", "value": 30}'
|
||||
result = soyaml.replacelistobject([filename, "key1", "name", "item3", json_obj])
|
||||
self.assertEqual(result, 1)
|
||||
self.assertIn("No list item found with name=item3", mock_stderr.getvalue())
|
||||
|
||||
# Verify file was not modified
|
||||
file = open(filename, "r")
|
||||
actual = file.read()
|
||||
file.close()
|
||||
self.assertIn("item1", actual)
|
||||
self.assertIn("item2", actual)
|
||||
self.assertNotIn("item3", actual)
|
||||
|
||||
def test_replacelistobject_key_noexist(self):
|
||||
filename = "/tmp/so-yaml_test-replacelistobject.yaml"
|
||||
file = open(filename, "w")
|
||||
file.write("{key1: [{name: item1}]}")
|
||||
file.close()
|
||||
|
||||
with patch('sys.exit', new=MagicMock()) as sysmock:
|
||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||
sys.argv = ["cmd", "replacelistobject", filename, "key2", "name", "item1", '{"name": "item2"}']
|
||||
soyaml.main()
|
||||
sysmock.assert_called()
|
||||
self.assertEqual("The key provided does not exist. No action was taken on the file.\n", mock_stderr.getvalue())
|
||||
|
||||
def test_replacelistobject_key_noexist_deep(self):
|
||||
filename = "/tmp/so-yaml_test-replacelistobject.yaml"
|
||||
file = open(filename, "w")
|
||||
file.write("{key1: { child1: [{name: a}] }}")
|
||||
file.close()
|
||||
|
||||
with patch('sys.exit', new=MagicMock()) as sysmock:
|
||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||
sys.argv = ["cmd", "replacelistobject", filename, "key1.child2", "name", "a", '{"name": "b"}']
|
||||
soyaml.main()
|
||||
sysmock.assert_called()
|
||||
self.assertEqual("The key provided does not exist. No action was taken on the file.\n", mock_stderr.getvalue())
|
||||
|
||||
def test_replacelistobject_key_nonlist(self):
|
||||
filename = "/tmp/so-yaml_test-replacelistobject.yaml"
|
||||
file = open(filename, "w")
|
||||
file.write("{key1: { child1: 123 }}")
|
||||
file.close()
|
||||
|
||||
with patch('sys.exit', new=MagicMock()) as sysmock:
|
||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||
sys.argv = ["cmd", "replacelistobject", filename, "key1", "name", "item", '{"name": "item"}']
|
||||
soyaml.main()
|
||||
sysmock.assert_called()
|
||||
self.assertEqual("The existing value for the given key is not a list. No action was taken on the file.\n", mock_stderr.getvalue())
|
||||
|
||||
def test_replacelistobject_key_nonlist_deep(self):
|
||||
filename = "/tmp/so-yaml_test-replacelistobject.yaml"
|
||||
file = open(filename, "w")
|
||||
file.write("{key1: { child1: 123, child2: { deep1: 45 } }}")
|
||||
file.close()
|
||||
|
||||
with patch('sys.exit', new=MagicMock()) as sysmock:
|
||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||
sys.argv = ["cmd", "replacelistobject", filename, "key1.child2.deep1", "name", "item", '{"name": "item"}']
|
||||
soyaml.main()
|
||||
sysmock.assert_called()
|
||||
self.assertEqual("The existing value for the given key is not a list. No action was taken on the file.\n", mock_stderr.getvalue())
|
||||
|
||||
def test_replacelistobject_string_condition_value(self):
|
||||
filename = "/tmp/so-yaml_test-replacelistobject.yaml"
|
||||
file = open(filename, "w")
|
||||
file.write("{key1: [{name: item1, value: 10}, {name: item2, value: 20}]}")
|
||||
file.close()
|
||||
|
||||
json_obj = '{"name": "item1", "value": 15}'
|
||||
soyaml.replacelistobject([filename, "key1", "name", "item1", json_obj])
|
||||
|
||||
file = open(filename, "r")
|
||||
actual = file.read()
|
||||
file.close()
|
||||
|
||||
expected = "key1:\n- name: item1\n value: 15\n- name: item2\n value: 20\n"
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
def test_replacelistobject_numeric_condition_value(self):
|
||||
filename = "/tmp/so-yaml_test-replacelistobject.yaml"
|
||||
file = open(filename, "w")
|
||||
file.write("{key1: [{id: '1', status: active}, {id: '2', status: inactive}]}")
|
||||
file.close()
|
||||
|
||||
json_obj = '{"id": "1", "status": "updated"}'
|
||||
soyaml.replacelistobject([filename, "key1", "id", "1", json_obj])
|
||||
|
||||
file = open(filename, "r")
|
||||
actual = file.read()
|
||||
file.close()
|
||||
|
||||
expected = "key1:\n- id: '1'\n status: updated\n- id: '2'\n status: inactive\n"
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
@@ -93,6 +93,10 @@ check_err() {
|
||||
161)
|
||||
echo 'Required intermediate Elasticsearch upgrade not complete'
|
||||
;;
|
||||
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 "Start soup again manually to continue the upgrade to Security Onion $originally_requested_so_version."
|
||||
;;
|
||||
*)
|
||||
echo 'Unhandled error'
|
||||
echo "$err_msg"
|
||||
@@ -154,7 +158,7 @@ EOF
|
||||
echo "Ensure you verify the ISO that you downloaded."
|
||||
exit 0
|
||||
else
|
||||
echo "Device has been mounted!"
|
||||
echo "Device has been mounted! $(cat /tmp/soagupdate/SecurityOnion/VERSION)"
|
||||
fi
|
||||
else
|
||||
echo "Could not find Security Onion ISO content at ${ISOLOC}"
|
||||
@@ -165,7 +169,7 @@ EOF
|
||||
}
|
||||
|
||||
airgap_update_dockers() {
|
||||
if [[ $is_airgap -eq 0 ]] || [[ $nonairgap_useiso -eq 0 ]]; then
|
||||
if [[ $is_airgap -eq 0 ]] || [[ ! -z "$ISOLOC" ]]; then
|
||||
# Let's copy the tarball
|
||||
if [[ ! -f $AGDOCKER/registry.tar ]]; then
|
||||
echo "Unable to locate registry. Exiting"
|
||||
@@ -200,24 +204,13 @@ update_registry() {
|
||||
check_airgap() {
|
||||
# See if this is an airgap install
|
||||
AIRGAP=$(cat /opt/so/saltstack/local/pillar/global/soc_global.sls | grep airgap: | awk '{print $2}' | tr '[:upper:]' '[:lower:]')
|
||||
if [[ ! -z "$ISOLOC" ]]; then
|
||||
# flag to use ISO for non-airgap installs, won't be used everywhere is_airgap -eq 0 is used. Used to speed up network soups by using local storage for large files.
|
||||
nonairgap_useiso=0
|
||||
else
|
||||
nonairgap_useiso=1
|
||||
fi
|
||||
|
||||
if [[ "$AIRGAP" == "true" ]]; then
|
||||
is_airgap=0
|
||||
else
|
||||
is_airgap=1
|
||||
fi
|
||||
|
||||
# use ISO if its airgap install OR ISOLOC was set with -f <path>
|
||||
if [[ "$AIRGAP" == "true" ]] || [[ $nonairgap_useiso -eq 0 ]]; then
|
||||
UPDATE_DIR=/tmp/soagupdate/SecurityOnion
|
||||
AGDOCKER=/tmp/soagupdate/docker
|
||||
AGREPO=/tmp/soagupdate/minimal/Packages
|
||||
else
|
||||
is_airgap=1
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -708,6 +701,21 @@ post_to_2.4.210() {
|
||||
echo "Regenerating Elastic Agent Installers"
|
||||
/sbin/so-elastic-agent-gen-installers
|
||||
|
||||
# migrate elasticsearch:managed_integrations pillar to manager:managed_integrations
|
||||
if managed_integrations=$(/usr/sbin/so-yaml.py get /opt/so/saltstack/local/pillar/elasticsearch/soc_elasticsearch.sls elasticsearch.managed_integrations 2>/dev/null); then
|
||||
local managed_integrations_old_pillar="/tmp/elasticsearch-managed_integrations.yaml"
|
||||
|
||||
echo "Migrating managed_integrations pillar"
|
||||
echo -e "$managed_integrations" > "$managed_integrations_old_pillar"
|
||||
|
||||
/usr/sbin/so-yaml.py add /opt/so/saltstack/local/pillar/manager/soc_manager.sls manager.managed_integrations file:$managed_integrations_old_pillar > /dev/null 2>&1
|
||||
|
||||
/usr/sbin/so-yaml.py remove /opt/so/saltstack/local/pillar/elasticsearch/soc_elasticsearch.sls elasticsearch.managed_integrations
|
||||
fi
|
||||
|
||||
# Remove so-rule-update script left behind by the idstools removal in 2.4.200
|
||||
rm -f /usr/sbin/so-rule-update
|
||||
|
||||
POSTVERSION=2.4.210
|
||||
}
|
||||
|
||||
@@ -995,7 +1003,9 @@ up_to_2.4.210() {
|
||||
# Elastic Update for this release, so download Elastic Agent files
|
||||
determine_elastic_agent_upgrade
|
||||
create_ca_pillar
|
||||
|
||||
# This state is used to deal with the breaking change introduced in 3006.17 - https://docs.saltproject.io/en/3006/topics/releases/3006.17.html
|
||||
# This is the only way the state is called so we can use concurrent=True
|
||||
salt-call state.apply salt.master.add_minimum_auth_version --file-root=$UPDATE_DIR/salt --local concurrent=True
|
||||
INSTALLEDVERSION=2.4.210
|
||||
}
|
||||
|
||||
@@ -1396,7 +1406,7 @@ so-yaml.py removelistitem /etc/salt/master file_roots.base /opt/so/rules/nids
|
||||
}
|
||||
|
||||
determine_elastic_agent_upgrade() {
|
||||
if [[ $is_airgap -eq 0 ]] || [[ $nonairgap_useiso -eq 0 ]]; then
|
||||
if [[ $is_airgap -eq 0 ]]; then
|
||||
update_elastic_agent_airgap
|
||||
else
|
||||
set +e
|
||||
@@ -1693,115 +1703,218 @@ verify_latest_update_script() {
|
||||
|
||||
verify_es_version_compatibility() {
|
||||
|
||||
local es_required_version_statefile="/opt/so/state/so_es_required_upgrade_version.txt"
|
||||
local es_verification_script="/tmp/so_intermediate_upgrade_verification.sh"
|
||||
# supported upgrade paths for SO-ES versions
|
||||
declare -A es_upgrade_map=(
|
||||
["8.14.3"]="8.17.3 8.18.4 8.18.6 8.18.8"
|
||||
["8.17.3"]="8.18.4 8.18.6 8.18.8"
|
||||
["8.18.4"]="8.18.6 8.18.8 9.0.8"
|
||||
["8.18.6"]="8.18.8 9.0.8"
|
||||
["8.18.8"]="9.0.8"
|
||||
)
|
||||
local es_required_version_statefile_base="/opt/so/state/so_es_required_upgrade_version"
|
||||
local es_verification_script="/tmp/so_intermediate_upgrade_verification.sh"
|
||||
local is_active_intermediate_upgrade=1
|
||||
# supported upgrade paths for SO-ES versions
|
||||
declare -A es_upgrade_map=(
|
||||
["8.14.3"]="8.17.3 8.18.4 8.18.6 8.18.8"
|
||||
["8.17.3"]="8.18.4 8.18.6 8.18.8"
|
||||
["8.18.4"]="8.18.6 8.18.8 9.0.8"
|
||||
["8.18.6"]="8.18.8 9.0.8"
|
||||
["8.18.8"]="9.0.8"
|
||||
)
|
||||
|
||||
# Elasticsearch MUST upgrade through these versions
|
||||
declare -A es_to_so_version=(
|
||||
["8.18.8"]="2.4.190-20251024"
|
||||
)
|
||||
# Elasticsearch MUST upgrade through these versions
|
||||
declare -A es_to_so_version=(
|
||||
["8.18.8"]="2.4.190-20251024"
|
||||
)
|
||||
|
||||
# Get current Elasticsearch version
|
||||
if es_version_raw=$(so-elasticsearch-query / --fail --retry 5 --retry-delay 10); then
|
||||
es_version=$(echo "$es_version_raw" | jq -r '.version.number' )
|
||||
else
|
||||
echo "Could not determine current Elasticsearch version to validate compatibility with post soup Elasticsearch version."
|
||||
exit 160
|
||||
fi
|
||||
|
||||
if ! target_es_version=$(so-yaml.py get $UPDATE_DIR/salt/elasticsearch/defaults.yaml elasticsearch.version | sed -n '1p'); then
|
||||
# so-yaml.py failed to get the ES version from upgrade versions elasticsearch/defaults.yaml file. Likely they are upgrading to an SO version older than 2.4.110 prior to the ES version pinning and should be OKAY to continue with the upgrade.
|
||||
|
||||
# if so-yaml.py failed to get the ES version AND the version we are upgrading to is newer than 2.4.110 then we should bail
|
||||
if [[ $(cat $UPDATE_DIR/VERSION | cut -d'.' -f3) > 110 ]]; then
|
||||
echo "Couldn't determine the target Elasticsearch version (post soup version) to ensure compatibility with current Elasticsearch version. Exiting"
|
||||
exit 160
|
||||
fi
|
||||
|
||||
# allow upgrade to version < 2.4.110 without checking ES version compatibility
|
||||
return 0
|
||||
|
||||
fi
|
||||
|
||||
# if this statefile exists then we have done an intermediate upgrade and we need to ensure that ALL ES nodes have been upgraded to the version in the statefile before allowing soup to continue
|
||||
if [[ -f "$es_required_version_statefile" ]]; then
|
||||
# required so verification script should have already been created
|
||||
if [[ ! -f "$es_verification_script" ]]; then
|
||||
create_intermediate_upgrade_verification_script $es_verification_script
|
||||
fi
|
||||
|
||||
local es_required_version_statefile_value=$(cat $es_required_version_statefile)
|
||||
echo -e "\n##############################################################################################################################\n"
|
||||
echo "A previously required intermediate Elasticsearch upgrade was detected. Verifying that all Searchnodes/Heavynodes have successfully upgraded Elasticsearch to $es_required_version_statefile_value before proceeding with soup to avoid potential data loss!"
|
||||
# create script using version in statefile
|
||||
timeout --foreground 4000 bash "$es_verification_script" "$es_required_version_statefile_value" "$es_required_version_statefile"
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo -e "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
|
||||
|
||||
echo "A previous required intermediate Elasticsearch upgrade to $es_required_version_statefile_value has yet to successfully complete across the grid. Please allow time for all Searchnodes/Heavynodes to have upgraded Elasticsearch to $es_required_version_statefile_value before running soup again to avoid potential data loss!"
|
||||
|
||||
echo -e "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
|
||||
exit 161
|
||||
fi
|
||||
echo -e "\n##############################################################################################################################\n"
|
||||
fi
|
||||
|
||||
if [[ " ${es_upgrade_map[$es_version]} " =~ " $target_es_version " || "$es_version" == "$target_es_version" ]]; then
|
||||
# supported upgrade
|
||||
return 0
|
||||
else
|
||||
compatible_versions=${es_upgrade_map[$es_version]}
|
||||
if [[ -z "$compatible_versions" ]]; then
|
||||
# If current ES version is not explicitly defined in the upgrade map, we know they have an intermediate upgrade to do.
|
||||
# We default to the lowest ES version defined in es_to_so_version as $first_es_required_version
|
||||
local first_es_required_version=$(printf '%s\n' "${!es_to_so_version[@]}" | sort -V | head -n1)
|
||||
next_step_so_version=${es_to_so_version[$first_es_required_version]}
|
||||
required_es_upgrade_version="$first_es_required_version"
|
||||
# Get current Elasticsearch version
|
||||
if es_version_raw=$(so-elasticsearch-query / --fail --retry 5 --retry-delay 10); then
|
||||
es_version=$(echo "$es_version_raw" | jq -r '.version.number' )
|
||||
else
|
||||
next_step_so_version=${es_to_so_version[${compatible_versions##* }]}
|
||||
required_es_upgrade_version="${compatible_versions##* }"
|
||||
echo "Could not determine current Elasticsearch version to validate compatibility with post soup Elasticsearch version."
|
||||
|
||||
exit 160
|
||||
fi
|
||||
echo -e "\n##############################################################################################################################\n"
|
||||
echo -e "You are currently running Security Onion $INSTALLEDVERSION. You will need to update to version $next_step_so_version before updating to $(cat $UPDATE_DIR/VERSION).\n"
|
||||
|
||||
echo "$required_es_upgrade_version" > "$es_required_version_statefile"
|
||||
if ! target_es_version_raw=$(so-yaml.py get $UPDATE_DIR/salt/elasticsearch/defaults.yaml elasticsearch.version); then
|
||||
# so-yaml.py failed to get the ES version from upgrade versions elasticsearch/defaults.yaml file. Likely they are upgrading to an SO version older than 2.4.110 prior to the ES version pinning and should be OKAY to continue with the upgrade.
|
||||
|
||||
# We expect to upgrade to the latest compatiable minor version of ES
|
||||
create_intermediate_upgrade_verification_script $es_verification_script
|
||||
# if so-yaml.py failed to get the ES version AND the version we are upgrading to is newer than 2.4.110 then we should bail
|
||||
if [[ $(cat $UPDATE_DIR/VERSION | cut -d'.' -f3) > 110 ]]; then
|
||||
echo "Couldn't determine the target Elasticsearch version (post soup version) to ensure compatibility with current Elasticsearch version. Exiting"
|
||||
|
||||
if [[ $is_airgap -eq 0 ]]; then
|
||||
echo "You can download the $next_step_so_version ISO image from https://download.securityonion.net/file/securityonion/securityonion-$next_step_so_version.iso"
|
||||
echo "*** Once you have updated to $next_step_so_version, you can then run soup again to update to $(cat $UPDATE_DIR/VERSION). ***"
|
||||
echo -e "\n##############################################################################################################################\n"
|
||||
exit 160
|
||||
exit 160
|
||||
fi
|
||||
|
||||
# allow upgrade to version < 2.4.110 without checking ES version compatibility
|
||||
return 0
|
||||
else
|
||||
# preserve BRANCH value if set originally
|
||||
if [[ -n "$BRANCH" ]]; then
|
||||
local originally_requested_so_version="$BRANCH"
|
||||
else
|
||||
local originally_requested_so_version="2.4/main"
|
||||
fi
|
||||
|
||||
echo "Starting automated intermediate upgrade to $next_step_so_version."
|
||||
echo "After completion, the system will automatically attempt to upgrade to the latest version."
|
||||
echo -e "\n##############################################################################################################################\n"
|
||||
exec bash -c "BRANCH=$next_step_so_version soup -y && BRANCH=$next_step_so_version soup -y && \
|
||||
echo -e \"\n##############################################################################################################################\n\" && \
|
||||
echo -e \"Verifying Elasticsearch was successfully upgraded to $required_es_upgrade_version across the grid. This part can take a while as Searchnodes/Heavynodes sync up with the Manager! \n\nOnce verification completes the next soup will begin automatically. If verification takes longer than 1 hour it will stop waiting and your grid will remain at $next_step_so_version. Allowing for all Searchnodes/Heavynodes to upgrade Elasticsearch to the required version on their own time.\n\" \
|
||||
&& timeout --foreground 4000 bash /tmp/so_intermediate_upgrade_verification.sh $required_es_upgrade_version $es_required_version_statefile && \
|
||||
echo -e \"\n##############################################################################################################################\n\" \
|
||||
&& BRANCH=$originally_requested_so_version soup -y && BRANCH=$originally_requested_so_version soup -y"
|
||||
target_es_version=$(sed -n '1p' <<< "$target_es_version_raw")
|
||||
fi
|
||||
fi
|
||||
|
||||
for statefile in "${es_required_version_statefile_base}"-*; do
|
||||
[[ -f $statefile ]] || continue
|
||||
|
||||
local es_required_version_statefile_value=$(cat "$statefile")
|
||||
|
||||
if [[ "$es_required_version_statefile_value" == "$target_es_version" ]]; then
|
||||
echo "Intermediate upgrade to ES $target_es_version is in progress. Skipping Elasticsearch version compatibility check."
|
||||
is_active_intermediate_upgrade=0
|
||||
continue
|
||||
fi
|
||||
|
||||
# use sort to check if es_required_statefile_value is < the current es_version.
|
||||
if [[ "$(printf '%s\n' $es_required_version_statefile_value $es_version | sort -V | head -n1)" == "$es_required_version_statefile_value" ]]; then
|
||||
rm -f "$statefile"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ ! -f "$es_verification_script" ]]; then
|
||||
create_intermediate_upgrade_verification_script "$es_verification_script"
|
||||
fi
|
||||
|
||||
echo -e "\n##############################################################################################################################\n"
|
||||
echo "A previously required intermediate Elasticsearch upgrade was detected. Verifying that all Searchnodes/Heavynodes have successfully upgraded Elasticsearch to $es_required_version_statefile_value before proceeding with soup to avoid potential data loss! This command can take up to an hour to complete."
|
||||
timeout --foreground 4000 bash "$es_verification_script" "$es_required_version_statefile_value" "$statefile"
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo -e "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
|
||||
|
||||
echo "A previous required intermediate Elasticsearch upgrade to $es_required_version_statefile_value has yet to successfully complete across the grid. Please allow time for all Searchnodes/Heavynodes to have upgraded Elasticsearch to $es_required_version_statefile_value before running soup again to avoid potential data loss!"
|
||||
|
||||
echo -e "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
|
||||
|
||||
exit 161
|
||||
fi
|
||||
echo -e "\n##############################################################################################################################\n"
|
||||
done
|
||||
|
||||
# if current soup is an intermediate upgrade we can skip the upgrade map check below
|
||||
if [[ $is_active_intermediate_upgrade -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ " ${es_upgrade_map[$es_version]} " =~ " $target_es_version " || "$es_version" == "$target_es_version" ]]; then
|
||||
# supported upgrade
|
||||
return 0
|
||||
else
|
||||
compatible_versions=${es_upgrade_map[$es_version]}
|
||||
if [[ -z "$compatible_versions" ]]; then
|
||||
# If current ES version is not explicitly defined in the upgrade map, we know they have an intermediate upgrade to do.
|
||||
# We default to the lowest ES version defined in es_to_so_version as $first_es_required_version
|
||||
local first_es_required_version=$(printf '%s\n' "${!es_to_so_version[@]}" | sort -V | head -n1)
|
||||
next_step_so_version=${es_to_so_version[$first_es_required_version]}
|
||||
required_es_upgrade_version="$first_es_required_version"
|
||||
else
|
||||
next_step_so_version=${es_to_so_version[${compatible_versions##* }]}
|
||||
required_es_upgrade_version="${compatible_versions##* }"
|
||||
fi
|
||||
echo -e "\n##############################################################################################################################\n"
|
||||
echo -e "You are currently running Security Onion $INSTALLEDVERSION. You will need to update to version $next_step_so_version before updating to $(cat $UPDATE_DIR/VERSION).\n"
|
||||
|
||||
es_required_version_statefile="${es_required_version_statefile_base}-${required_es_upgrade_version}"
|
||||
echo "$required_es_upgrade_version" > "$es_required_version_statefile"
|
||||
|
||||
# We expect to upgrade to the latest compatiable minor version of ES
|
||||
create_intermediate_upgrade_verification_script "$es_verification_script"
|
||||
|
||||
if [[ $is_airgap -eq 0 ]]; then
|
||||
run_airgap_intermediate_upgrade
|
||||
else
|
||||
if [[ ! -z $ISOLOC ]]; then
|
||||
originally_requested_iso_location="$ISOLOC"
|
||||
fi
|
||||
# Make sure ISOLOC is not set. Network installs that used soup -f would have ISOLOC set.
|
||||
unset ISOLOC
|
||||
|
||||
run_network_intermediate_upgrade
|
||||
fi
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
run_airgap_intermediate_upgrade() {
|
||||
local originally_requested_so_version=$(cat $UPDATE_DIR/VERSION)
|
||||
# preserve ISOLOC value, so we can try to use it post intermediate upgrade
|
||||
local originally_requested_iso_location="$ISOLOC"
|
||||
|
||||
# make sure a fresh ISO gets mounted
|
||||
unmount_update
|
||||
|
||||
echo "You can download the $next_step_so_version ISO image from https://download.securityonion.net/file/securityonion/securityonion-$next_step_so_version.iso"
|
||||
echo -e "\nIf you have the next ISO / USB ready, enter the path now eg. /dev/sdd, /home/onion/securityonion-$next_step_so_version.iso:"
|
||||
|
||||
while [[ -z "$next_iso_location" ]] || [[ ! -f "$next_iso_location" && ! -b "$next_iso_location" ]]; do
|
||||
# List removable devices if any are present
|
||||
local removable_devices=$(lsblk -no PATH,SIZE,TYPE,MOUNTPOINTS,RM | awk '$NF==1')
|
||||
if [[ -n "$removable_devices" ]]; then
|
||||
echo "PATH SIZE TYPE MOUNTPOINTS RM"
|
||||
echo "$removable_devices"
|
||||
fi
|
||||
|
||||
read -rp "Device/ISO Path (or 'exit' to quit): " next_iso_location
|
||||
if [[ "${next_iso_location,,}" == "exit" ]]; then
|
||||
echo "Exiting soup. Before reattempting to upgrade to $originally_requested_so_version, please first upgrade to $next_step_so_version to ensure Elasticsearch can properly update through the required versions."
|
||||
|
||||
exit 160
|
||||
fi
|
||||
|
||||
if [[ ! -f "$next_iso_location" && ! -b "$next_iso_location" ]]; then
|
||||
echo "$next_iso_location is not a valid file or block device."
|
||||
next_iso_location=""
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Using $next_iso_location for required intermediary upgrade."
|
||||
exec bash <<EOF
|
||||
ISOLOC=$next_iso_location soup -y && \
|
||||
ISOLOC=$next_iso_location soup -y && \
|
||||
|
||||
echo -e "\n##############################################################################################################################\n" && \
|
||||
echo -e "Verifying Elasticsearch was successfully upgraded to $required_es_upgrade_version across the grid. This part can take a while as Searchnodes/Heavynodes sync up with the Manager! \n\nOnce verification completes the next soup will begin automatically. If verification takes longer than 1 hour it will stop waiting and your grid will remain at $next_step_so_version. Allowing for all Searchnodes/Heavynodes to upgrade Elasticsearch to the required version on their own time.\n" && \
|
||||
|
||||
timeout --foreground 4000 bash /tmp/so_intermediate_upgrade_verification.sh $required_es_upgrade_version $es_required_version_statefile && \
|
||||
|
||||
echo -e "\n##############################################################################################################################\n" && \
|
||||
|
||||
# automatically start the next soup if the original ISO isn't using the same block device we just used
|
||||
if [[ -n "$originally_requested_iso_location" ]] && [[ "$originally_requested_iso_location" != "$next_iso_location" ]]; then
|
||||
umount /tmp/soagupdate
|
||||
ISOLOC=$originally_requested_iso_location soup -y && \
|
||||
ISOLOC=$originally_requested_iso_location soup -y
|
||||
else
|
||||
echo "Could not automatically start next soup to $originally_requested_so_version. Soup will now exit here at $(cat /etc/soversion)" && \
|
||||
|
||||
exit 170
|
||||
fi
|
||||
|
||||
echo -e "\n##############################################################################################################################\n"
|
||||
EOF
|
||||
}
|
||||
|
||||
run_network_intermediate_upgrade() {
|
||||
# preserve BRANCH value if set originally
|
||||
if [[ -n "$BRANCH" ]]; then
|
||||
local originally_requested_so_branch="$BRANCH"
|
||||
else
|
||||
local originally_requested_so_branch="2.4/main"
|
||||
fi
|
||||
|
||||
echo "Starting automated intermediate upgrade to $next_step_so_version."
|
||||
echo "After completion, the system will automatically attempt to upgrade to the latest version."
|
||||
echo -e "\n##############################################################################################################################\n"
|
||||
exec bash << EOF
|
||||
BRANCH=$next_step_so_version soup -y && \
|
||||
BRANCH=$next_step_so_version soup -y && \
|
||||
|
||||
echo -e "\n##############################################################################################################################\n" && \
|
||||
echo -e "Verifying Elasticsearch was successfully upgraded to $required_es_upgrade_version across the grid. This part can take a while as Searchnodes/Heavynodes sync up with the Manager! \n\nOnce verification completes the next soup will begin automatically. If verification takes longer than 1 hour it will stop waiting and your grid will remain at $next_step_so_version. Allowing for all Searchnodes/Heavynodes to upgrade Elasticsearch to the required version on their own time.\n" && \
|
||||
|
||||
timeout --foreground 4000 bash /tmp/so_intermediate_upgrade_verification.sh $required_es_upgrade_version $es_required_version_statefile && \
|
||||
|
||||
echo -e "\n##############################################################################################################################\n" && \
|
||||
if [[ -n "$originally_requested_iso_location" ]]; then
|
||||
# nonairgap soup that used -f originally, runs intermediate upgrade using network + BRANCH, later coming back to the original ISO for the last soup
|
||||
ISOLOC=$originally_requested_iso_location soup -y && \
|
||||
ISOLOC=$originally_requested_iso_location soup -y
|
||||
else
|
||||
BRANCH=$originally_requested_so_branch soup -y && \
|
||||
BRANCH=$originally_requested_so_branch soup -y
|
||||
fi
|
||||
echo -e "\n##############################################################################################################################\n"
|
||||
EOF
|
||||
}
|
||||
|
||||
create_intermediate_upgrade_verification_script() {
|
||||
@@ -1962,7 +2075,7 @@ apply_hotfix() {
|
||||
mv /etc/pki/managerssl.crt /etc/pki/managerssl.crt.old
|
||||
mv /etc/pki/managerssl.key /etc/pki/managerssl.key.old
|
||||
systemctl_func "start" "salt-minion"
|
||||
(wait_for_salt_minion "$MINIONID" "120" "4" "$SOUP_LOG" || fail "Salt minion was not running or ready.") 2>&1 | tee -a "$SOUP_LOG"
|
||||
wait_for_salt_minion "$MINIONID" "120" "4" "$SOUP_LOG" || fail "Salt minion was not running or ready."
|
||||
fi
|
||||
else
|
||||
echo "No actions required. ($INSTALLEDVERSION/$HOTFIXVERSION)"
|
||||
@@ -2014,10 +2127,15 @@ main() {
|
||||
MINION_ROLE=$(lookup_role)
|
||||
echo "Found that Security Onion $INSTALLEDVERSION is currently installed."
|
||||
echo ""
|
||||
if [[ $is_airgap -eq 0 ]] || [[ $nonairgap_useiso -eq 0 ]]; then
|
||||
# Let's mount the ISO since this is airgap or non-airgap with -f used
|
||||
if [[ $is_airgap -eq 0 ]]; then
|
||||
# Let's mount the ISO since this is airgap
|
||||
airgap_mounted
|
||||
else
|
||||
# if not airgap but -f was used
|
||||
if [[ ! -z "$ISOLOC" ]]; then
|
||||
airgap_mounted
|
||||
AGDOCKER=/tmp/soagupdate/docker
|
||||
fi
|
||||
echo "Cloning Security Onion github repo into $UPDATE_DIR."
|
||||
echo "Removing previous upgrade sources."
|
||||
rm -rf $UPDATE_DIR
|
||||
@@ -2027,6 +2145,7 @@ main() {
|
||||
echo "Verifying we have the latest soup script."
|
||||
verify_latest_update_script
|
||||
|
||||
echo "Verifying Elasticsearch version compatibility before upgrading."
|
||||
verify_es_version_compatibility
|
||||
|
||||
echo "Let's see if we need to update Security Onion."
|
||||
@@ -2037,8 +2156,7 @@ main() {
|
||||
upgrade_check_salt
|
||||
set -e
|
||||
|
||||
if [[ $is_airgap -eq 0 ]] || [[ $nonairgap_useiso -eq 0 ]]; then
|
||||
# non-airgap with -f used can do an initial ISO repo update and so-repo-sync cron job will sync any diff later via network
|
||||
if [[ $is_airgap -eq 0 ]]; then
|
||||
update_airgap_repo
|
||||
dnf clean all
|
||||
check_os_updates
|
||||
@@ -2079,7 +2197,7 @@ main() {
|
||||
else
|
||||
update_registry
|
||||
set +e
|
||||
update_docker_containers 'soup' '' '' '/dev/stdout' 2>&1 | tee -a "$SOUP_LOG"
|
||||
update_docker_containers 'soup' '' '' '/dev/stdout' 2>&1
|
||||
set -e
|
||||
fi
|
||||
|
||||
@@ -2157,7 +2275,7 @@ main() {
|
||||
echo ""
|
||||
echo "Running a highstate. This could take several minutes."
|
||||
set +e
|
||||
(wait_for_salt_minion "$MINIONID" "120" "4" "$SOUP_LOG" || fail "Salt minion was not running or ready.") 2>&1 | tee -a "$SOUP_LOG"
|
||||
wait_for_salt_minion "$MINIONID" "120" "4" "$SOUP_LOG" || fail "Salt minion was not running or ready."
|
||||
highstate
|
||||
set -e
|
||||
|
||||
@@ -2170,10 +2288,15 @@ main() {
|
||||
check_saltmaster_status
|
||||
|
||||
echo "Running a highstate to complete the Security Onion upgrade on this manager. This could take several minutes."
|
||||
(wait_for_salt_minion "$MINIONID" "120" "4" "$SOUP_LOG" || fail "Salt minion was not running or ready.") 2>&1 | tee -a "$SOUP_LOG"
|
||||
wait_for_salt_minion "$MINIONID" "120" "4" "$SOUP_LOG" || fail "Salt minion was not running or ready."
|
||||
|
||||
# Stop long-running scripts to allow potentially updated scripts to load on the next execution.
|
||||
killall salt-relay.sh
|
||||
if pgrep salt-relay.sh > /dev/null 2>&1; then
|
||||
echo "Stopping salt-relay.sh"
|
||||
killall salt-relay.sh
|
||||
else
|
||||
echo "salt-relay.sh is not running"
|
||||
fi
|
||||
|
||||
# ensure the mine is updated and populated before highstates run, following the salt-master restart
|
||||
update_salt_mine
|
||||
|
||||
184
salt/manager/tools/sbin/soupto3
Executable file
184
salt/manager/tools/sbin/soupto3
Executable file
@@ -0,0 +1,184 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
|
||||
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
|
||||
# https://securityonion.net/license; you may not use this file except in compliance with the
|
||||
# Elastic License 2.0.
|
||||
|
||||
|
||||
. /usr/sbin/so-common
|
||||
|
||||
UPDATE_URL=https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/refs/heads/3/main/VERSION
|
||||
|
||||
# Check if already running version 3
|
||||
CURRENT_VERSION=$(cat /etc/soversion 2>/dev/null)
|
||||
if [[ "$CURRENT_VERSION" =~ ^3\. ]]; then
|
||||
echo ""
|
||||
echo "========================================================================="
|
||||
echo " Already Running Security Onion 3"
|
||||
echo "========================================================================="
|
||||
echo ""
|
||||
echo " This system is already running Security Onion $CURRENT_VERSION."
|
||||
echo " Use 'soup' to update within the 3.x release line."
|
||||
echo ""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Checking PCAP settings."
|
||||
echo ""
|
||||
|
||||
# Check pcapengine setting - must be SURICATA before upgrading to version 3
|
||||
PCAP_ENGINE=$(lookup_pillar "pcapengine")
|
||||
|
||||
PCAP_DELETED=false
|
||||
|
||||
prompt_delete_pcap() {
|
||||
read -rp " Would you like to delete all remaining Stenographer PCAP data? (y/N): " DELETE_PCAP
|
||||
if [[ "$DELETE_PCAP" =~ ^[Yy]$ ]]; then
|
||||
echo ""
|
||||
echo " WARNING: This will permanently delete all Stenographer PCAP data"
|
||||
echo " on all nodes. This action cannot be undone."
|
||||
echo ""
|
||||
read -rp " Are you sure? (y/N): " CONFIRM_DELETE
|
||||
if [[ "$CONFIRM_DELETE" =~ ^[Yy]$ ]]; then
|
||||
echo ""
|
||||
echo " Deleting Stenographer PCAP data on all nodes..."
|
||||
salt '*' cmd.run "rm -rf /nsm/pcap/* && rm -rf /nsm/pcapindex/*"
|
||||
echo " Done."
|
||||
PCAP_DELETED=true
|
||||
else
|
||||
echo ""
|
||||
echo " Delete cancelled."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
pcapengine_not_changed() {
|
||||
echo ""
|
||||
echo " PCAP engine must be set to SURICATA before upgrading to Security Onion 3."
|
||||
echo " You can change this in SOC by navigating to:"
|
||||
echo " Configuration -> global -> pcapengine"
|
||||
}
|
||||
|
||||
prompt_change_engine() {
|
||||
local current_engine=$1
|
||||
echo ""
|
||||
read -rp " Would you like to change the PCAP engine to SURICATA now? (y/N): " CHANGE_ENGINE
|
||||
if [[ "$CHANGE_ENGINE" =~ ^[Yy]$ ]]; then
|
||||
if [[ "$PCAP_DELETED" != "true" ]]; then
|
||||
echo ""
|
||||
echo " WARNING: Stenographer PCAP data was not deleted. If you proceed,"
|
||||
echo " this data will no longer be accessible through SOC and will never"
|
||||
echo " be automatically deleted. You will need to manually remove it later."
|
||||
echo ""
|
||||
read -rp " Continue with changing pcapengine to SURICATA? (y/N): " CONFIRM_CHANGE
|
||||
if [[ ! "$CONFIRM_CHANGE" =~ ^[Yy]$ ]]; then
|
||||
pcapengine_not_changed
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
echo " Updating PCAP engine to SURICATA..."
|
||||
so-yaml.py replace /opt/so/saltstack/local/pillar/global/soc_global.sls global.pcapengine SURICATA
|
||||
echo " Done."
|
||||
return 0
|
||||
else
|
||||
pcapengine_not_changed
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
case "$PCAP_ENGINE" in
|
||||
SURICATA)
|
||||
echo "PCAP engine settings OK."
|
||||
;;
|
||||
TRANSITION|STENO)
|
||||
echo ""
|
||||
echo "========================================================================="
|
||||
echo " PCAP Engine Check Failed"
|
||||
echo "========================================================================="
|
||||
echo ""
|
||||
echo " Your PCAP engine is currently set to $PCAP_ENGINE."
|
||||
echo ""
|
||||
echo " Before upgrading to Security Onion 3, Stenographer PCAP data must be"
|
||||
echo " removed and the PCAP engine must be set to SURICATA."
|
||||
echo ""
|
||||
echo " To check remaining Stenographer PCAP usage, run:"
|
||||
echo " salt '*' cmd.run 'du -sh /nsm/pcap'"
|
||||
echo ""
|
||||
|
||||
prompt_delete_pcap
|
||||
if ! prompt_change_engine "$PCAP_ENGINE"; then
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo ""
|
||||
echo "========================================================================="
|
||||
echo " PCAP Engine Check Failed"
|
||||
echo "========================================================================="
|
||||
echo ""
|
||||
echo " Unable to determine the PCAP engine setting (got: '$PCAP_ENGINE')."
|
||||
echo " Please ensure the PCAP engine is set to SURICATA."
|
||||
echo " In SOC, navigate to Configuration -> global -> pcapengine"
|
||||
echo " and change the value to SURICATA."
|
||||
echo ""
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
echo "Checking Versions."
|
||||
echo ""
|
||||
|
||||
# Check if Security Onion 3 has been released
|
||||
VERSION=$(curl -sSf "$UPDATE_URL" 2>/dev/null)
|
||||
|
||||
if [[ -z "$VERSION" ]]; then
|
||||
echo ""
|
||||
echo "========================================================================="
|
||||
echo " Unable to Check Version"
|
||||
echo "========================================================================="
|
||||
echo ""
|
||||
echo " Could not retrieve version information from:"
|
||||
echo " $UPDATE_URL"
|
||||
echo ""
|
||||
echo " Please check your network connection and try again."
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$VERSION" == "UNRELEASED" ]]; then
|
||||
echo ""
|
||||
echo "========================================================================="
|
||||
echo " Security Onion 3 Not Available"
|
||||
echo "========================================================================="
|
||||
echo ""
|
||||
echo " Security Onion 3 has not been released yet."
|
||||
echo ""
|
||||
echo " Please check back later or visit https://securityonion.net for updates."
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate version format (e.g., 3.0.2)
|
||||
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo ""
|
||||
echo "========================================================================="
|
||||
echo " Invalid Version"
|
||||
echo "========================================================================="
|
||||
echo ""
|
||||
echo " Received unexpected version format: '$VERSION'"
|
||||
echo ""
|
||||
echo " Please check back later or visit https://securityonion.net for updates."
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Security Onion 3 ($VERSION) is available. Upgrading..."
|
||||
echo ""
|
||||
|
||||
# All checks passed - proceed with upgrade
|
||||
BRANCH=3/main soup
|
||||
73
salt/salt/engines/master/minimum_auth_version.py
Normal file
73
salt/salt/engines/master/minimum_auth_version.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# 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.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
import salt.client
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
TIMESTAMP_FILE = '/opt/so/state/mav_engine_start_time'
|
||||
|
||||
def _get_start_time():
|
||||
"""Read persisted start time from file, or create one if it doesn't exist."""
|
||||
if os.path.exists(TIMESTAMP_FILE):
|
||||
with open(TIMESTAMP_FILE, 'r') as f:
|
||||
timestamp = f.read().strip()
|
||||
start_time = datetime.fromisoformat(timestamp)
|
||||
log.info("Loaded existing start time from %s: %s", TIMESTAMP_FILE, start_time)
|
||||
return start_time
|
||||
|
||||
start_time = datetime.now()
|
||||
with open(TIMESTAMP_FILE, 'w') as f:
|
||||
f.write(start_time.isoformat())
|
||||
log.info("No existing start time found. Persisted new start time: %s", start_time)
|
||||
return start_time
|
||||
|
||||
|
||||
def _clear_start_time():
|
||||
"""Remove the persisted timestamp file after successful completion."""
|
||||
if os.path.exists(TIMESTAMP_FILE):
|
||||
os.remove(TIMESTAMP_FILE)
|
||||
log.info("Removed timestamp file %s", TIMESTAMP_FILE)
|
||||
|
||||
|
||||
def start(wait_days=7):
|
||||
"""
|
||||
This engine waits for the specified number of days, then changes minimum_auth_version.
|
||||
|
||||
Args:
|
||||
wait_days: Days to wait before taking action (default: 7)
|
||||
"""
|
||||
log.info(
|
||||
"Starting minimum_auth_version engine - Wait time: %d days",
|
||||
wait_days
|
||||
)
|
||||
|
||||
start_time = _get_start_time()
|
||||
wait_delta = timedelta(days=wait_days)
|
||||
mav_removed = False
|
||||
caller = salt.client.Caller()
|
||||
|
||||
while True:
|
||||
if not mav_removed:
|
||||
elapsed = datetime.now() - start_time
|
||||
|
||||
if elapsed >= wait_delta:
|
||||
log.info("Changing minimum_auth_version")
|
||||
_clear_start_time()
|
||||
result = caller.cmd('state.apply', 'salt.master.remove_minimum_auth_version', queue=True)
|
||||
# We shouldn't reach this line since the above line should remove the engine and restart salt-master
|
||||
log.info("State apply result: %s", result)
|
||||
mav_removed = True
|
||||
else:
|
||||
target_time = start_time + wait_delta
|
||||
log.info("minimum_auth_version will be changed within an hour of %s", target_time.strftime('%m-%d-%Y %H:%M'))
|
||||
|
||||
time.sleep(3600) # Check hourly
|
||||
@@ -1,4 +1,4 @@
|
||||
# version cannot be used elsewhere in this pillar as soup is grepping for it to determine if Salt needs to be patched
|
||||
salt:
|
||||
master:
|
||||
version: '3006.16'
|
||||
version: '3006.19'
|
||||
|
||||
23
salt/salt/master/add_minimum_auth_version.sls
Normal file
23
salt/salt/master/add_minimum_auth_version.sls
Normal file
@@ -0,0 +1,23 @@
|
||||
# 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.
|
||||
|
||||
# This state is to be used during soup preupgrade_changes, and run when the salt-master has been stopped. Soup will later start the salt-master.
|
||||
# This state is used to deal with the breaking change introduced in 3006.17 - https://docs.saltproject.io/en/3006/topics/releases/3006.17.html
|
||||
|
||||
|
||||
set_minimum_auth_version_0:
|
||||
file.managed:
|
||||
- name: /etc/salt/master.d/minimum_auth_version.conf
|
||||
- source: salt://salt/master/files/minimum_auth_version.conf
|
||||
|
||||
add_minimum_auth_version_engine_config:
|
||||
file.managed:
|
||||
- name: /etc/salt/master.d/minimum_auth_version_engine.conf
|
||||
- source: salt://salt/master/files/minimum_auth_version_engine.conf
|
||||
|
||||
add_minimum_auth_version_engine:
|
||||
file.managed:
|
||||
- name: /etc/salt/engines/minimum_auth_version.py
|
||||
- source: salt://salt/engines/master/minimum_auth_version.py
|
||||
1
salt/salt/master/files/minimum_auth_version.conf
Normal file
1
salt/salt/master/files/minimum_auth_version.conf
Normal file
@@ -0,0 +1 @@
|
||||
minimum_auth_version: 0
|
||||
3
salt/salt/master/files/minimum_auth_version_engine.conf
Normal file
3
salt/salt/master/files/minimum_auth_version_engine.conf
Normal file
@@ -0,0 +1,3 @@
|
||||
engines:
|
||||
- minimum_auth_version:
|
||||
wait_days: 7
|
||||
21
salt/salt/master/remove_minimum_auth_version.sls
Normal file
21
salt/salt/master/remove_minimum_auth_version.sls
Normal file
@@ -0,0 +1,21 @@
|
||||
# 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.
|
||||
|
||||
include:
|
||||
- salt.master
|
||||
|
||||
unset_minimum_auth_version_0:
|
||||
file.absent:
|
||||
- name: /etc/salt/master.d/minimum_auth_version.conf
|
||||
|
||||
remove_minimum_auth_version_engine_config:
|
||||
file.absent:
|
||||
- name: /etc/salt/master.d/minimum_auth_version_engine.conf
|
||||
|
||||
remove_minimum_auth_version_engine:
|
||||
file.absent:
|
||||
- name: /etc/salt/engines/minimum_auth_version.py
|
||||
- watch_in:
|
||||
- service: salt_master_service
|
||||
@@ -1,5 +1,5 @@
|
||||
# version cannot be used elsewhere in this pillar as soup is grepping for it to determine if Salt needs to be patched
|
||||
salt:
|
||||
minion:
|
||||
version: '3006.16'
|
||||
version: '3006.19'
|
||||
check_threshold: 3600 # in seconds, threshold used for so-salt-minion-check. any value less than 600 seconds may cause a lot of salt-minion restarts since the job to touch the file occurs every 5-8 minutes by default
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#======================================================================================================================
|
||||
set -o nounset # Treat unset variables as an error
|
||||
|
||||
__ScriptVersion="2025.09.03"
|
||||
__ScriptVersion="2026.01.22"
|
||||
__ScriptName="bootstrap-salt.sh"
|
||||
|
||||
__ScriptFullName="$0"
|
||||
@@ -369,7 +369,7 @@ __usage() {
|
||||
also be specified. Salt installation will be ommitted, but some of the
|
||||
dependencies could be installed to write configuration with -j or -J.
|
||||
-d Disables checking if Salt services are enabled to start on system boot.
|
||||
You can also do this by touching ${BS_TMP_DIR}/disable_salt_checks on the target
|
||||
You can also do this by touching ${_TMP_DIR}/disable_salt_checks on the target
|
||||
host. Default: \${BS_FALSE}
|
||||
-D Show debug output
|
||||
-f Force shallow cloning for git installations.
|
||||
@@ -2819,14 +2819,25 @@ __install_salt_from_repo() {
|
||||
${_pip_cmd} install --force-reinstall --break-system-packages "${_arch_dep}"
|
||||
fi
|
||||
|
||||
echodebug "Running '${_pip_cmd} install ${_USE_BREAK_SYSTEM_PACKAGES} --no-deps --force-reinstall ${_PIP_INSTALL_ARGS} ${_TMP_DIR}/git/deps/salt*.whl'"
|
||||
_PIP_VERSION_STRING=$(${_pip_cmd} --version)
|
||||
echodebug "Installed pip version: $_PIP_VERSION_STRING"
|
||||
_PIP_MAJOR_VERSION=$(echo "$_PIP_VERSION_STRING" | sed -E 's/^pip ([0-9]+)\..*/\1/')
|
||||
|
||||
echodebug "Running ${_pip_cmd} install ${_USE_BREAK_SYSTEM_PACKAGES} --no-deps --force-reinstall ${_PIP_INSTALL_ARGS} --global-option=--salt-config-dir=$_SALT_ETC_DIR --salt-cache-dir=${_SALT_CACHE_DIR} ${SETUP_PY_INSTALL_ARGS} ${_TMP_DIR}/git/deps/salt*.whl"
|
||||
|
||||
${_pip_cmd} install ${_USE_BREAK_SYSTEM_PACKAGES} --no-deps --force-reinstall \
|
||||
${_PIP_INSTALL_ARGS} \
|
||||
--global-option="--salt-config-dir=$_SALT_ETC_DIR --salt-cache-dir=${_SALT_CACHE_DIR} ${SETUP_PY_INSTALL_ARGS}" \
|
||||
${_TMP_DIR}/git/deps/salt*.whl || return 1
|
||||
# The following branching can be removed once we no longer support distros that still ship with
|
||||
# versions of `pip` earlier than v22.1 such as Debian 11
|
||||
if [ "$_PIP_MAJOR_VERSION" -lt 23 ]; then
|
||||
echodebug "Running ${_pip_cmd} install ${_USE_BREAK_SYSTEM_PACKAGES} --no-deps --force-reinstall ${_PIP_INSTALL_ARGS} --global-option=--salt-config-dir=$_SALT_ETC_DIR --salt-cache-dir=${_SALT_CACHE_DIR} ${SETUP_PY_INSTALL_ARGS} ${_TMP_DIR}/git/deps/salt*.whl"
|
||||
${_pip_cmd} install ${_USE_BREAK_SYSTEM_PACKAGES} --no-deps --force-reinstall \
|
||||
${_PIP_INSTALL_ARGS} \
|
||||
--global-option="--salt-config-dir=$_SALT_ETC_DIR --salt-cache-dir=${_SALT_CACHE_DIR} ${SETUP_PY_INSTALL_ARGS}" \
|
||||
${_TMP_DIR}/git/deps/salt*.whl || return 1
|
||||
else
|
||||
echodebug "Running ${_pip_cmd} install ${_USE_BREAK_SYSTEM_PACKAGES} --no-deps --force-reinstall ${_PIP_INSTALL_ARGS} --config-settings=--global-option=--salt-config-dir=$_SALT_ETC_DIR --salt-cache-dir=${_SALT_CACHE_DIR} ${SETUP_PY_INSTALL_ARGS} ${_TMP_DIR}/git/deps/salt*.whl"
|
||||
${_pip_cmd} install ${_USE_BREAK_SYSTEM_PACKAGES} --no-deps --force-reinstall \
|
||||
${_PIP_INSTALL_ARGS} \
|
||||
--config-settings="--global-option=--salt-config-dir=$_SALT_ETC_DIR --salt-cache-dir=${_SALT_CACHE_DIR} ${SETUP_PY_INSTALL_ARGS}" \
|
||||
${_TMP_DIR}/git/deps/salt*.whl || return 1
|
||||
fi
|
||||
|
||||
echoinfo "Checking if Salt can be imported using ${_py_exe}"
|
||||
CHECK_SALT_SCRIPT=$(cat << EOM
|
||||
@@ -6096,7 +6107,14 @@ install_arch_linux_git_deps() {
|
||||
}
|
||||
|
||||
install_arch_linux_onedir_deps() {
|
||||
echodebug "install_arch_linux_onedir_deps() entry"
|
||||
|
||||
# Basic tooling for download/verify/extract
|
||||
pacman -Sy --noconfirm --needed wget tar gzip gnupg ca-certificates || return 1
|
||||
|
||||
# Reuse stable deps for python-yaml etc. if you want config_salt() parity
|
||||
install_arch_linux_stable_deps || return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
install_arch_linux_stable() {
|
||||
@@ -6111,7 +6129,73 @@ install_arch_linux_stable() {
|
||||
pacman -S --noconfirm --needed bash || return 1
|
||||
pacman -Su --noconfirm || return 1
|
||||
# We can now resume regular salt update
|
||||
pacman -Syu --noconfirm salt || return 1
|
||||
# Except that this hasn't been in arch repos for years;
|
||||
# so we have to build from AUR
|
||||
# We use "buildgirl" because Eve demanded it.
|
||||
build_user=${build_user:-buildgirl}
|
||||
userdel "$build_user" || true
|
||||
useradd -M -r -s /usr/bin/nologin "$build_user"
|
||||
echo "$build_user ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/"$build_user"
|
||||
rm -rf /tmp/yay-bin || true
|
||||
|
||||
git clone https://aur.archlinux.org/salt.git /tmp/yay-bin
|
||||
chown -R "$build_user":"$build_user" /tmp/yay-bin
|
||||
sudo -u "$build_user" env -i \
|
||||
HOME=/tmp \
|
||||
PATH=/usr/bin:/bin:/usr/sbin:/sbin \
|
||||
MAKEFLAGS="-j$(nproc)" \
|
||||
LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 \
|
||||
makepkg -CcsiD /tmp/yay-bin \
|
||||
--noconfirm --needed \
|
||||
--noprogressbar || return 1
|
||||
|
||||
rm -f /etc/sudoers.d/"$build_user"
|
||||
rm -rf /tmp/yay-bin
|
||||
userdel "$build_user"
|
||||
return 0
|
||||
}
|
||||
|
||||
install_arch_linux_onedir() {
|
||||
echodebug "install_arch_linux_onedir() entry"
|
||||
|
||||
version="${ONEDIR_REV:-latest}"
|
||||
arch="x86_64"
|
||||
[ "$(uname -m)" = "aarch64" ] && arch="aarch64"
|
||||
|
||||
# Resolve "latest" to actual version
|
||||
if [ "$version" = "latest" ]; then
|
||||
version=$(wget -qO- https://api.github.com/repos/saltstack/salt/releases/latest \
|
||||
| grep -Eo '"tag_name": *"v[0-9.]+"' \
|
||||
| sed 's/"tag_name": *"v//;s/"//') || return 1
|
||||
fi
|
||||
|
||||
tarball="salt-${version}-onedir-linux-${arch}.tar.xz"
|
||||
url="https://github.com/saltstack/salt/releases/download/v${version}/${tarball}"
|
||||
extractdir="/tmp/salt-${version}-onedir-linux-${arch}"
|
||||
|
||||
echoinfo "Downloading Salt onedir: $url"
|
||||
wget -q "$url" -O "/tmp/${tarball}" || return 1
|
||||
|
||||
# Validate tarball
|
||||
if ! tar -tf "/tmp/${tarball}" >/dev/null 2>&1; then
|
||||
echoerror "Invalid or corrupt onedir tarball"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Prepare extraction
|
||||
rm -rf "$extractdir" || true
|
||||
rm -rf /opt/saltstack/salt || true
|
||||
mkdir -p "$extractdir"
|
||||
|
||||
# Extract and flatten (remove leading 'salt/' directory)
|
||||
# /tmp/salt-${version}-onedir-linux-${arch}
|
||||
tar --strip-components=1 -xf "/tmp/${tarball}" -C "$extractdir"
|
||||
|
||||
# Place into /opt
|
||||
mkdir -p /opt/saltstack/salt
|
||||
mv "$extractdir"/* /opt/saltstack/salt/ || return 1
|
||||
chmod -R 755 /opt/saltstack/salt
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -6249,17 +6333,48 @@ install_arch_check_services() {
|
||||
return 0
|
||||
}
|
||||
|
||||
install_arch_linux_onedir() {
|
||||
install_arch_linux_stable || return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
install_arch_linux_onedir_post() {
|
||||
install_arch_linux_post || return 1
|
||||
echodebug "install_arch_linux_onedir_post() entry"
|
||||
|
||||
return 0
|
||||
# Disable any distro/AUR salt units
|
||||
systemctl disable --now salt-minion.service 2>/dev/null || true
|
||||
systemctl disable --now salt-master.service 2>/dev/null || true
|
||||
|
||||
# Drop a clean unit, same pattern as Debian/Ubuntu onedir
|
||||
cat >/etc/systemd/system/salt-minion.service <<'EOF'
|
||||
[Unit]
|
||||
Description=Salt Minion (onedir)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/opt/saltstack/salt/salt-minion -c /etc/salt
|
||||
Restart=always
|
||||
LimitNOFILE=100000
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
|
||||
# Add onedir paths system-wide
|
||||
cat >/etc/profile.d/saltstack.sh <<'EOF'
|
||||
export PATH=/opt/saltstack/salt:/opt/saltstack/salt/bin:$PATH
|
||||
EOF
|
||||
|
||||
chmod 644 /etc/profile.d/saltstack.sh
|
||||
|
||||
if [ "$_START_DAEMONS" -eq $BS_TRUE ]; then
|
||||
systemctl enable --now salt-minion.service
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
#
|
||||
# Ended Arch Install Functions
|
||||
#
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user