mirror of
https://github.com/Security-Onion-Solutions/securityonion.git
synced 2026-06-12 21:29:16 +02:00
Compare commits
95 Commits
rotatehype
...
soupmod2
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ee555957a | |||
| 43f72c1f9f | |||
| ae6a705ce1 | |||
| b1273573ed | |||
| 6c42c419e2 | |||
| f23652397c | |||
| 07d3b148b5 | |||
| 780d9faf0d | |||
| d2fe51d5fe | |||
| 0cc94980af | |||
| b8bf684077 | |||
| f083db67e4 | |||
| 83aaa76f98 | |||
| 3ba96da3b7 | |||
| f0712bd780 | |||
| 448668a72e | |||
| f088a27159 | |||
| 9f5a9616a5 | |||
| 27c7702325 | |||
| 8c306eb37d | |||
| e536ffa363 | |||
| eb82f9ea9d | |||
| d7aa7ab228 | |||
| fe0b68d24c | |||
| 6ad345730b | |||
| 9580976ba2 | |||
| ac907ba45f | |||
| f957954abf | |||
| cb3631da81 | |||
| f5d63f585e | |||
| 13f8be40b5 | |||
| 9ee90a5bc0 | |||
| ca85c5d900 | |||
| 2d653b6f1b | |||
| 34fee25b0c | |||
| 1d3d98f759 | |||
| a767c79641 | |||
| 61e72c89e4 | |||
| d9fb7313f9 | |||
| 7ca2313255 | |||
| 534f0e639d | |||
| 559465b407 | |||
| f9c2579261 | |||
| 33699a914b | |||
| 0c2d8f8973 | |||
| f2996fb888 | |||
| 3c533cccbc | |||
| 79da9f9f2c | |||
| 99a027589b | |||
| 68a82a425b | |||
| d86a3c5cc9 | |||
| 86edc5aaba | |||
| 9a70a06b3b | |||
| 526d739b3b | |||
| 68d783e760 | |||
| 1e9b6b0975 | |||
| 2131e7d450 | |||
| 2a2d853ac4 | |||
| 5abd6de4b5 | |||
| bb8ae91d91 | |||
| 93ffce98d7 | |||
| 5599cce22c | |||
| b2a82fec29 | |||
| 613eca52fc | |||
| 79987f3659 | |||
| bf609a112e | |||
| 0b4a4de609 | |||
| ad376d2a43 | |||
| 0834998cca | |||
| 473f93f0ee | |||
| 16055c4d88 | |||
| 7cc2e045fb | |||
| 6955ee73bf | |||
| c0272ddb81 | |||
| d72219c586 | |||
| ffd34d4e0e | |||
| aa78978740 | |||
| 75d4f5e496 | |||
| 89a28d2cfe | |||
| c1d187599b | |||
| d87313db27 | |||
| 141a61f5b5 | |||
| 901cbf03e4 | |||
| b485be4602 | |||
| 7d13007aa9 | |||
| d7a1b67095 | |||
| 6c8997b28a | |||
| 58f1d08ebe | |||
| d0aa33a255 | |||
| e45ad45d73 | |||
| fabecb8288 | |||
| 907f699721 | |||
| e7a7047f71 | |||
| 936295f1c4 | |||
| 61ca60a94c |
@@ -11,6 +11,7 @@ body:
|
|||||||
-
|
-
|
||||||
- 3.0.0
|
- 3.0.0
|
||||||
- 3.1.0
|
- 3.1.0
|
||||||
|
- 3.2.0
|
||||||
- Other (please provide detail below)
|
- Other (please provide detail below)
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
+11
-11
@@ -1,17 +1,17 @@
|
|||||||
### 3.0.0-20260331 ISO image released on 2026/03/31
|
### 3.1.0-20260528 ISO image released on 2026/05/28
|
||||||
|
|
||||||
|
|
||||||
### Download and Verify
|
### Download and Verify
|
||||||
|
|
||||||
3.0.0-20260331 ISO image:
|
3.1.0-20260528 ISO image:
|
||||||
https://download.securityonion.net/file/securityonion/securityonion-3.0.0-20260331.iso
|
https://download.securityonion.net/file/securityonion/securityonion-3.1.0-20260528.iso
|
||||||
|
|
||||||
MD5: ECD318A1662A6FDE0EF213F5A9BD4B07
|
MD5: 9D6FF58DEEE24089D722C73169765B3E
|
||||||
SHA1: E55BE314440CCF3392DC0B06BC5E270B43176D9C
|
SHA1: 2B8B816B6CEC3B7F96B3C5E040EBF502DD2C412F
|
||||||
SHA256: 7FC47405E335CBE5C2B6C51FE7AC60248F35CBE504907B8B5A33822B23F8F4D5
|
SHA256: 62FAB57E247C843D6A04F0796D8162C732B65D82FC3E4A59D087135B9FD32912
|
||||||
|
|
||||||
Signature for ISO image:
|
Signature for ISO image:
|
||||||
https://github.com/Security-Onion-Solutions/securityonion/raw/3/main/sigs/securityonion-3.0.0-20260331.iso.sig
|
https://github.com/Security-Onion-Solutions/securityonion/raw/3/main/sigs/securityonion-3.1.0-20260528.iso.sig
|
||||||
|
|
||||||
Signing key:
|
Signing key:
|
||||||
https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/3/main/KEYS
|
https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/3/main/KEYS
|
||||||
@@ -25,22 +25,22 @@ wget https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/3/
|
|||||||
|
|
||||||
Download the signature file for the ISO:
|
Download the signature file for the ISO:
|
||||||
```
|
```
|
||||||
wget https://github.com/Security-Onion-Solutions/securityonion/raw/3/main/sigs/securityonion-3.0.0-20260331.iso.sig
|
wget https://github.com/Security-Onion-Solutions/securityonion/raw/3/main/sigs/securityonion-3.1.0-20260528.iso.sig
|
||||||
```
|
```
|
||||||
|
|
||||||
Download the ISO image:
|
Download the ISO image:
|
||||||
```
|
```
|
||||||
wget https://download.securityonion.net/file/securityonion/securityonion-3.0.0-20260331.iso
|
wget https://download.securityonion.net/file/securityonion/securityonion-3.1.0-20260528.iso
|
||||||
```
|
```
|
||||||
|
|
||||||
Verify the downloaded ISO image using the signature file:
|
Verify the downloaded ISO image using the signature file:
|
||||||
```
|
```
|
||||||
gpg --verify securityonion-3.0.0-20260331.iso.sig securityonion-3.0.0-20260331.iso
|
gpg --verify securityonion-3.1.0-20260528.iso.sig securityonion-3.1.0-20260528.iso
|
||||||
```
|
```
|
||||||
|
|
||||||
The output should show "Good signature" and the Primary key fingerprint should match what's shown below:
|
The output should show "Good signature" and the Primary key fingerprint should match what's shown below:
|
||||||
```
|
```
|
||||||
gpg: Signature made Mon 30 Mar 2026 06:22:14 PM EDT using RSA key ID FE507013
|
gpg: Signature made Wed 27 May 2026 03:03:59 PM EDT using RSA key ID FE507013
|
||||||
gpg: Good signature from "Security Onion Solutions, LLC <info@securityonionsolutions.com>"
|
gpg: Good signature from "Security Onion Solutions, LLC <info@securityonionsolutions.com>"
|
||||||
gpg: WARNING: This key is not certified with a trusted signature!
|
gpg: WARNING: This key is not certified with a trusted signature!
|
||||||
gpg: There is no indication that the signature belongs to the owner.
|
gpg: There is no indication that the signature belongs to the owner.
|
||||||
|
|||||||
@@ -25,9 +25,11 @@ if [ ! -f $BACKUPFILE ]; then
|
|||||||
# Create empty backup file
|
# Create empty backup file
|
||||||
tar -cf $BACKUPFILE -T /dev/null
|
tar -cf $BACKUPFILE -T /dev/null
|
||||||
|
|
||||||
# Loop through all paths defined in global.sls, and append them to backup file
|
# Loop through all paths defined in global.sls, and append them to backup file if they exist
|
||||||
{%- for LOCATION in BACKUPLOCATIONS %}
|
{%- for LOCATION in BACKUPLOCATIONS %}
|
||||||
|
if [[ -d {{ LOCATION }} || -f {{ LOCATION }} ]]; then
|
||||||
tar -rf $BACKUPFILE "${EXCLUSIONS[@]}" {{ LOCATION }}
|
tar -rf $BACKUPFILE "${EXCLUSIONS[@]}" {{ LOCATION }}
|
||||||
|
fi
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ include:
|
|||||||
wait_for_elasticsearch_elasticfleet:
|
wait_for_elasticsearch_elasticfleet:
|
||||||
cmd.run:
|
cmd.run:
|
||||||
- name: so-elasticsearch-wait
|
- name: so-elasticsearch-wait
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if GLOBALS.role == "so-fleet" %}
|
||||||
# Sync Elastic Agent artifacts to Fleet Node
|
# Sync Elastic Agent artifacts to Fleet Node
|
||||||
elasticagent_syncartifacts:
|
elasticagent_syncartifacts:
|
||||||
file.recurse:
|
file.recurse:
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ include:
|
|||||||
- elasticfleet.config
|
- elasticfleet.config
|
||||||
|
|
||||||
# If enabled, automatically update Fleet Logstash Outputs
|
# If enabled, automatically update Fleet Logstash Outputs
|
||||||
{% if ELASTICFLEETMERGED.config.server.enable_auto_configuration and grains.role not in ['so-import', 'so-eval'] %}
|
{% if ELASTICFLEETMERGED.config.server.enable_auto_configuration %}
|
||||||
|
{% if grains.role not in ['so-import', 'so-eval']%}
|
||||||
so-elastic-fleet-auto-configure-logstash-outputs:
|
so-elastic-fleet-auto-configure-logstash-outputs:
|
||||||
cmd.run:
|
cmd.run:
|
||||||
- name: /usr/sbin/so-elastic-fleet-outputs-update
|
- name: /usr/sbin/so-elastic-fleet-outputs-update
|
||||||
@@ -27,6 +28,7 @@ so-elastic-fleet-auto-configure-server-urls:
|
|||||||
- retry:
|
- retry:
|
||||||
attempts: 4
|
attempts: 4
|
||||||
interval: 30
|
interval: 30
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
# Automatically update Fleet Server Elasticsearch URLs & Agent Artifact URLs
|
# Automatically update Fleet Server Elasticsearch URLs & Agent Artifact URLs
|
||||||
so-elastic-fleet-auto-configure-elasticsearch-urls:
|
so-elastic-fleet-auto-configure-elasticsearch-urls:
|
||||||
|
|||||||
@@ -30,6 +30,70 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Max number of concurrent Fleet write jobs (create/update). Override via env if needed.
|
||||||
|
MAX_FLEET_JOBS=${MAX_FLEET_JOBS:-10}
|
||||||
|
|
||||||
|
# Block until fewer than MAX_FLEET_JOBS background jobs are running.
|
||||||
|
elastic_fleet_throttle() {
|
||||||
|
while (( $(jobs -rp | wc -l) >= MAX_FLEET_JOBS )); do
|
||||||
|
wait -n
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load every integration JSON in a directory into a single agent policy.
|
||||||
|
# The agent policy is fetched ONCE (not per file), and the create/update writes
|
||||||
|
# are dispatched as throttled background jobs.
|
||||||
|
# $1 AGENT_POLICY - the agent policy id/name to load integrations into
|
||||||
|
# $2 DIR - directory of integration *.json files
|
||||||
|
# $3 LABEL - human-readable label for log output
|
||||||
|
# $4 SKIP_CREATE_NAME - (optional) integration name to skip when creating (still updated if present)
|
||||||
|
# Returns 1 if any integration failed to create/update.
|
||||||
|
elastic_fleet_load_integrations_dir() {
|
||||||
|
local AGENT_POLICY=$1
|
||||||
|
local DIR=$2
|
||||||
|
local LABEL=$3
|
||||||
|
local SKIP_CREATE_NAME=$4
|
||||||
|
local POLICY_JSON FAIL_FILE INTEGRATION NAME ID
|
||||||
|
|
||||||
|
FAIL_FILE=$(mktemp)
|
||||||
|
|
||||||
|
# Fetch the agent policy a single time; we look up integration ids locally below.
|
||||||
|
POLICY_JSON=$(fleet_api "agent_policies/$AGENT_POLICY")
|
||||||
|
|
||||||
|
for INTEGRATION in "$DIR"/*.json; do
|
||||||
|
[ -e "$INTEGRATION" ] || continue
|
||||||
|
NAME=$(jq -r .name "$INTEGRATION")
|
||||||
|
ID=$(jq -r --arg n "$NAME" '.item.package_policies[]? | select(.name==$n) | .id' <<<"$POLICY_JSON")
|
||||||
|
|
||||||
|
elastic_fleet_throttle
|
||||||
|
{
|
||||||
|
if [ -n "$ID" ]; then
|
||||||
|
printf "\n\n%s - Updating integration %s\n" "$LABEL" "$NAME"
|
||||||
|
if ! elastic_fleet_integration_update "$ID" "@$INTEGRATION"; then
|
||||||
|
flock 9; echo "update ${INTEGRATION##*/}" >&9
|
||||||
|
fi
|
||||||
|
elif [ -n "$SKIP_CREATE_NAME" ] && [ "$NAME" == "$SKIP_CREATE_NAME" ]; then
|
||||||
|
printf "\n\n%s - Skipping creation of %s\n" "$LABEL" "$NAME"
|
||||||
|
else
|
||||||
|
printf "\n\n%s - Creating integration %s\n" "$LABEL" "$NAME"
|
||||||
|
if ! elastic_fleet_integration_create "@$INTEGRATION"; then
|
||||||
|
flock 9; echo "create ${INTEGRATION##*/}" >&9
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
} 9>>"$FAIL_FILE" &
|
||||||
|
done
|
||||||
|
wait
|
||||||
|
|
||||||
|
local rc=0
|
||||||
|
if [ -s "$FAIL_FILE" ]; then
|
||||||
|
printf "\n%s: failed integrations:\n" "$LABEL"
|
||||||
|
cat "$FAIL_FILE"
|
||||||
|
rc=1
|
||||||
|
fi
|
||||||
|
rm -f "$FAIL_FILE"
|
||||||
|
return $rc
|
||||||
|
}
|
||||||
|
|
||||||
elastic_fleet_integration_check() {
|
elastic_fleet_integration_check() {
|
||||||
|
|
||||||
AGENT_POLICY=$1
|
AGENT_POLICY=$1
|
||||||
|
|||||||
@@ -18,93 +18,26 @@ if [ ! -f /opt/so/state/eaintegrations.txt ]; then
|
|||||||
# Third, configure Elastic Defend Integration seperately
|
# Third, configure Elastic Defend Integration seperately
|
||||||
/usr/sbin/so-elastic-fleet-integration-policy-elastic-defend
|
/usr/sbin/so-elastic-fleet-integration-policy-elastic-defend
|
||||||
|
|
||||||
|
# Each group fetches its agent policy once and dispatches create/update writes concurrently.
|
||||||
|
|
||||||
# Initial Endpoints
|
# Initial Endpoints
|
||||||
for INTEGRATION in /opt/so/conf/elastic-fleet/integrations/endpoints-initial/*.json; do
|
elastic_fleet_load_integrations_dir "endpoints-initial" \
|
||||||
printf "\n\nInitial Endpoints Policy - Loading $INTEGRATION\n"
|
/opt/so/conf/elastic-fleet/integrations/endpoints-initial "Initial Endpoints Policy" || RETURN_CODE=1
|
||||||
elastic_fleet_integration_check "endpoints-initial" "$INTEGRATION"
|
|
||||||
if [ -n "$INTEGRATION_ID" ]; then
|
|
||||||
printf "\n\nIntegration $NAME exists - Updating integration\n"
|
|
||||||
if ! elastic_fleet_integration_update "$INTEGRATION_ID" "@$INTEGRATION"; then
|
|
||||||
echo -e "\nFailed to update integration for ${INTEGRATION##*/}"
|
|
||||||
RETURN_CODE=1
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
printf "\n\nIntegration does not exist - Creating integration\n"
|
|
||||||
if ! elastic_fleet_integration_create "@$INTEGRATION"; then
|
|
||||||
echo -e "\nFailed to create integration for ${INTEGRATION##*/}"
|
|
||||||
RETURN_CODE=1
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Grid Nodes - General
|
# Grid Nodes - General
|
||||||
for INTEGRATION in /opt/so/conf/elastic-fleet/integrations/grid-nodes_general/*.json; do
|
elastic_fleet_load_integrations_dir "so-grid-nodes_general" \
|
||||||
printf "\n\nGrid Nodes Policy_General - Loading $INTEGRATION\n"
|
/opt/so/conf/elastic-fleet/integrations/grid-nodes_general "Grid Nodes Policy_General" || RETURN_CODE=1
|
||||||
elastic_fleet_integration_check "so-grid-nodes_general" "$INTEGRATION"
|
|
||||||
if [ -n "$INTEGRATION_ID" ]; then
|
|
||||||
printf "\n\nIntegration $NAME exists - Updating integration\n"
|
|
||||||
if ! elastic_fleet_integration_update "$INTEGRATION_ID" "@$INTEGRATION"; then
|
|
||||||
echo -e "\nFailed to update integration for ${INTEGRATION##*/}"
|
|
||||||
RETURN_CODE=1
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
printf "\n\nIntegration does not exist - Creating integration\n"
|
|
||||||
if ! elastic_fleet_integration_create "@$INTEGRATION"; then
|
|
||||||
echo -e "\nFailed to create integration for ${INTEGRATION##*/}"
|
|
||||||
RETURN_CODE=1
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Grid Nodes - Heavy
|
# Grid Nodes - Heavy
|
||||||
for INTEGRATION in /opt/so/conf/elastic-fleet/integrations/grid-nodes_heavy/*.json; do
|
elastic_fleet_load_integrations_dir "so-grid-nodes_heavy" \
|
||||||
printf "\n\nGrid Nodes Policy_Heavy - Loading $INTEGRATION\n"
|
/opt/so/conf/elastic-fleet/integrations/grid-nodes_heavy "Grid Nodes Policy_Heavy" || RETURN_CODE=1
|
||||||
elastic_fleet_integration_check "so-grid-nodes_heavy" "$INTEGRATION"
|
|
||||||
if [ -n "$INTEGRATION_ID" ]; then
|
|
||||||
printf "\n\nIntegration $NAME exists - Updating integration\n"
|
|
||||||
if ! elastic_fleet_integration_update "$INTEGRATION_ID" "@$INTEGRATION"; then
|
|
||||||
echo -e "\nFailed to update integration for ${INTEGRATION##*/}"
|
|
||||||
RETURN_CODE=1
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
printf "\n\nIntegration does not exist - Creating integration\n"
|
|
||||||
if ! elastic_fleet_integration_create "@$INTEGRATION"; then
|
|
||||||
echo -e "\nFailed to create integration for ${INTEGRATION##*/}"
|
|
||||||
RETURN_CODE=1
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Fleet Server - Optional integrations
|
# Fleet Server - Optional integrations (one agent policy per FleetServer_* directory)
|
||||||
for INTEGRATION in /opt/so/conf/elastic-fleet/integrations-optional/FleetServer*/*.json; do
|
for FLEET_DIR in /opt/so/conf/elastic-fleet/integrations-optional/FleetServer*/; do
|
||||||
if ! [ "$INTEGRATION" == "/opt/so/conf/elastic-fleet/integrations-optional/FleetServer*/*.json" ]; then
|
[ -d "$FLEET_DIR" ] || continue
|
||||||
FLEET_POLICY=`echo "$INTEGRATION"| cut -d'/' -f7`
|
FLEET_POLICY=$(basename "$FLEET_DIR")
|
||||||
printf "\n\nFleet Server Policy - Loading $INTEGRATION\n"
|
elastic_fleet_load_integrations_dir "$FLEET_POLICY" \
|
||||||
elastic_fleet_integration_check "$FLEET_POLICY" "$INTEGRATION"
|
"${FLEET_DIR%/}" "Fleet Server Policy" "elasticsearch-logs" || RETURN_CODE=1
|
||||||
if [ -n "$INTEGRATION_ID" ]; then
|
|
||||||
printf "\n\nIntegration $NAME exists - Updating integration\n"
|
|
||||||
if ! elastic_fleet_integration_update "$INTEGRATION_ID" "@$INTEGRATION"; then
|
|
||||||
echo -e "\nFailed to update integration for ${INTEGRATION##*/}"
|
|
||||||
RETURN_CODE=1
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
printf "\n\nIntegration does not exist - Creating integration\n"
|
|
||||||
if [ "$NAME" != "elasticsearch-logs" ]; then
|
|
||||||
if ! elastic_fleet_integration_create "@$INTEGRATION"; then
|
|
||||||
echo -e "\nFailed to create integration for ${INTEGRATION##*/}"
|
|
||||||
RETURN_CODE=1
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
|
|
||||||
# Only create the state file if all policies were created/updated successfully
|
# Only create the state file if all policies were created/updated successfully
|
||||||
|
|||||||
@@ -23,73 +23,90 @@ if [ $? -ne 0 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
default_packages=({% for pkg in SUPPORTED_PACKAGES %}"{{ pkg }}"{% if not loop.last %} {% endif %}{% endfor %})
|
default_packages=({% for pkg in SUPPORTED_PACKAGES %}"{{ pkg }}"{% if not loop.last %} {% endif %}{% endfor %})
|
||||||
|
# JSON array of the default packages, used by the jq filter below.
|
||||||
|
default_packages_json=$(printf '%s\n' "${default_packages[@]}" | jq -R . | jq -s '.')
|
||||||
|
|
||||||
|
# Output lock (serializes concurrent job output) and failure file (one marker line per
|
||||||
|
# failed integration). Mirrors the pattern used by elastic_fleet_load_integrations_dir.
|
||||||
|
OUTPUT_LOCK=$(mktemp)
|
||||||
|
FAIL_FILE=$(mktemp)
|
||||||
|
trap 'rm -f "$OUTPUT_LOCK" "$FAIL_FILE"' EXIT
|
||||||
|
|
||||||
|
# Cache of package name -> latest available version, so the same package is only looked up
|
||||||
|
# once instead of once per (policy, integration).
|
||||||
|
declare -A LATEST_VERSION_CACHE
|
||||||
|
|
||||||
ERROR=false
|
|
||||||
for AGENT_POLICY in $agent_policies; do
|
for AGENT_POLICY in $agent_policies; do
|
||||||
if ! integrations=$(elastic_fleet_integration_policy_names "$AGENT_POLICY"); then
|
# Fetch the agent policy a single time; package name/version and integration id are all
|
||||||
|
# extracted locally below instead of re-fetching the same policy per integration.
|
||||||
|
if ! POLICY_JSON=$(fleet_api "agent_policies/$AGENT_POLICY"); then
|
||||||
# this script upgrades default integration packages, exit 1 and let salt handle retrying
|
# this script upgrades default integration packages, exit 1 and let salt handle retrying
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
for INTEGRATION in $integrations; do
|
|
||||||
if ! [[ "$INTEGRATION" == "elastic-defend-endpoints" ]] && ! [[ "$INTEGRATION" == "fleet_server-"* ]]; then
|
|
||||||
# Get package name so we know what package to look for when checking the current and latest available version
|
|
||||||
if ! PACKAGE_NAME=$(elastic_fleet_integration_policy_package_name "$AGENT_POLICY" "$INTEGRATION"); then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
{%- if not AUTO_UPGRADE_INTEGRATIONS %}
|
|
||||||
if [[ " ${default_packages[@]} " =~ " $PACKAGE_NAME " ]]; then
|
|
||||||
{%- endif %}
|
|
||||||
# Get currently installed version of package
|
|
||||||
attempt=0
|
|
||||||
max_attempts=3
|
|
||||||
while [ $attempt -lt $max_attempts ]; do
|
|
||||||
if PACKAGE_VERSION=$(elastic_fleet_integration_policy_package_version "$AGENT_POLICY" "$INTEGRATION") && AVAILABLE_VERSION=$(elastic_fleet_package_latest_version_check "$PACKAGE_NAME"); then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
attempt=$((attempt + 1))
|
|
||||||
done
|
|
||||||
if [ $attempt -eq $max_attempts ]; then
|
|
||||||
echo "Error: Failed getting $PACKAGE_VERSION or $AVAILABLE_VERSION"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Get integration ID
|
# One jq pass emits name/package.name/package.version/id for every eligible integration.
|
||||||
if ! INTEGRATION_ID=$(elastic_fleet_integration_id "$AGENT_POLICY" "$INTEGRATION"); then
|
# The endpoint/fleet_server skips and the default-package gate are applied here in jq.
|
||||||
|
# $defaults (not $def, a jq reserved keyword) holds the default package list.
|
||||||
|
while IFS=$'\t' read -r INTEGRATION PACKAGE_NAME PACKAGE_VERSION INTEGRATION_ID; do
|
||||||
|
[ -n "$INTEGRATION" ] || continue
|
||||||
|
|
||||||
|
# Look up the latest available version once per package, then memoize it.
|
||||||
|
if [[ -z "${LATEST_VERSION_CACHE[$PACKAGE_NAME]+set}" ]]; then
|
||||||
|
if ! AVAILABLE_VERSION=$(elastic_fleet_package_latest_version_check "$PACKAGE_NAME"); then
|
||||||
|
echo "Error: Failed getting latest version for $PACKAGE_NAME"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
LATEST_VERSION_CACHE[$PACKAGE_NAME]=$AVAILABLE_VERSION
|
||||||
|
fi
|
||||||
|
AVAILABLE_VERSION=${LATEST_VERSION_CACHE[$PACKAGE_NAME]}
|
||||||
|
|
||||||
if [[ "$PACKAGE_VERSION" != "$AVAILABLE_VERSION" ]]; then
|
if [[ "$PACKAGE_VERSION" != "$AVAILABLE_VERSION" ]]; then
|
||||||
# Dry run of the upgrade
|
# Dry run, then (if clean) the actual upgrade, dispatched as a throttled background
|
||||||
echo ""
|
# job. Each job builds its full log into one block, then flushes it under a single
|
||||||
echo "Current $PACKAGE_NAME package version ($PACKAGE_VERSION) is not the same as the latest available package ($AVAILABLE_VERSION)..."
|
# shared lock (OUTPUT_LOCK) so concurrent jobs never interleave on stdout; a failed
|
||||||
echo "Upgrading $INTEGRATION..."
|
# job also appends a marker line to FAIL_FILE while holding that same lock.
|
||||||
echo "Starting dry run..."
|
elastic_fleet_throttle
|
||||||
|
{
|
||||||
|
block=$'\n'"Current $PACKAGE_NAME package version ($PACKAGE_VERSION) is not the same as the latest available package ($AVAILABLE_VERSION)..."$'\n'
|
||||||
|
block+="Upgrading $INTEGRATION..."$'\n'"Starting dry run..."$'\n'
|
||||||
|
fail=""
|
||||||
if ! DRYRUN_OUTPUT=$(elastic_fleet_integration_policy_dryrun_upgrade "$INTEGRATION_ID"); then
|
if ! DRYRUN_OUTPUT=$(elastic_fleet_integration_policy_dryrun_upgrade "$INTEGRATION_ID"); then
|
||||||
exit 1
|
block+="Error: Failed to complete dry run for '$INTEGRATION_ID'."$'\n'
|
||||||
fi
|
fail="dryrun $INTEGRATION"
|
||||||
DRYRUN_ERRORS=$(echo "$DRYRUN_OUTPUT" | jq .[].hasErrors)
|
elif [[ "$(jq .[].hasErrors <<<"$DRYRUN_OUTPUT")" == "false" ]]; then
|
||||||
|
block+="No errors detected. Proceeding with upgrade..."$'\n'
|
||||||
# If no errors with dry run, proceed with actual upgrade
|
|
||||||
if [[ "$DRYRUN_ERRORS" == "false" ]]; then
|
|
||||||
echo "No errors detected. Proceeding with upgrade..."
|
|
||||||
if ! elastic_fleet_integration_policy_upgrade "$INTEGRATION_ID"; then
|
if ! elastic_fleet_integration_policy_upgrade "$INTEGRATION_ID"; then
|
||||||
echo "Error: Upgrade failed for $PACKAGE_NAME with integration ID '$INTEGRATION_ID'."
|
block+="Error: Upgrade failed for $PACKAGE_NAME with integration ID '$INTEGRATION_ID'."$'\n'
|
||||||
ERROR=true
|
fail="upgrade $INTEGRATION"
|
||||||
continue
|
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "Errors detected during dry run for $PACKAGE_NAME policy upgrade..."
|
block+="Errors detected during dry run for $PACKAGE_NAME policy upgrade..."$'\n'
|
||||||
ERROR=true
|
fail="dryrun-errors $INTEGRATION"
|
||||||
continue
|
|
||||||
fi
|
fi
|
||||||
|
{
|
||||||
|
flock 9
|
||||||
|
printf '%s' "$block"
|
||||||
|
[ -n "$fail" ] && printf '%s\n' "$fail" >>"$FAIL_FILE"
|
||||||
|
} 9>>"$OUTPUT_LOCK"
|
||||||
|
} &
|
||||||
fi
|
fi
|
||||||
|
done < <(jq -r --argjson defaults "$default_packages_json" '
|
||||||
|
.item.package_policies[]
|
||||||
|
| select(.name != "elastic-defend-endpoints")
|
||||||
|
| select(.name | startswith("fleet_server-") | not)
|
||||||
{%- if not AUTO_UPGRADE_INTEGRATIONS %}
|
{%- if not AUTO_UPGRADE_INTEGRATIONS %}
|
||||||
fi
|
| select(.package.name | IN($defaults[]))
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
fi
|
| [.name, .package.name, .package.version, .id] | @tsv
|
||||||
done
|
' <<<"$POLICY_JSON")
|
||||||
done
|
done
|
||||||
if [[ "$ERROR" == "true" ]]; then
|
|
||||||
|
# Barrier: wait for every dispatched dry-run/upgrade job to finish.
|
||||||
|
wait
|
||||||
|
|
||||||
|
if [ -s "$FAIL_FILE" ]; then
|
||||||
|
printf '\nFailed integration upgrades:\n'
|
||||||
|
cat "$FAIL_FILE"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo
|
echo
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
STATE_FILE_SUCCESS=/opt/so/state/estemplates.txt
|
STATE_FILE_SUCCESS=/opt/so/state/estemplates.txt
|
||||||
INSTALLED_PACKAGE_LIST=/tmp/esfleet_installed_packages.json
|
INSTALLED_PACKAGE_LIST=/tmp/esfleet_installed_packages.json
|
||||||
BULK_INSTALL_PACKAGE_LIST=/tmp/esfleet_bulk_install.json
|
BULK_INSTALL_PACKAGE_LIST=/tmp/esfleet_bulk_install.json
|
||||||
BULK_INSTALL_PACKAGE_TMP=/tmp/esfleet_bulk_install_tmp.json
|
|
||||||
BULK_INSTALL_OUTPUT=/opt/so/state/esfleet_bulk_install_results.json
|
BULK_INSTALL_OUTPUT=/opt/so/state/esfleet_bulk_install_results.json
|
||||||
INTEGRATION_PACKAGE_COMPONENTS=/opt/so/state/esfleet_package_components.json
|
INTEGRATION_PACKAGE_COMPONENTS=/opt/so/state/esfleet_package_components.json
|
||||||
INPUT_PACKAGE_COMPONENTS=/opt/so/state/esfleet_input_package_components.json
|
INPUT_PACKAGE_COMPONENTS=/opt/so/state/esfleet_input_package_components.json
|
||||||
@@ -29,29 +28,6 @@ PENDING_UPDATE=false
|
|||||||
# Requiring some level of manual Elastic Stack configuration before installation
|
# Requiring some level of manual Elastic Stack configuration before installation
|
||||||
EXCLUDED_INTEGRATIONS=('apm')
|
EXCLUDED_INTEGRATIONS=('apm')
|
||||||
|
|
||||||
version_conversion(){
|
|
||||||
version=$1
|
|
||||||
echo "$version" | awk -F '.' '{ printf("%d%03d%03d\n", $1, $2, $3); }'
|
|
||||||
}
|
|
||||||
|
|
||||||
compare_versions() {
|
|
||||||
version1=$1
|
|
||||||
version2=$2
|
|
||||||
|
|
||||||
# Convert versions to numbers
|
|
||||||
num1=$(version_conversion "$version1")
|
|
||||||
num2=$(version_conversion "$version2")
|
|
||||||
|
|
||||||
# Compare using bc
|
|
||||||
if (( $(echo "$num1 < $num2" | bc -l) )); then
|
|
||||||
echo "less"
|
|
||||||
elif (( $(echo "$num1 > $num2" | bc -l) )); then
|
|
||||||
echo "greater"
|
|
||||||
else
|
|
||||||
echo "equal"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
IFS=$'\n'
|
IFS=$'\n'
|
||||||
agent_policies=$(elastic_fleet_agent_policy_ids)
|
agent_policies=$(elastic_fleet_agent_policy_ids)
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
@@ -63,23 +39,23 @@ default_packages=({% for pkg in SUPPORTED_PACKAGES %}"{{ pkg }}"{% if not loop.l
|
|||||||
|
|
||||||
in_use_integrations=()
|
in_use_integrations=()
|
||||||
|
|
||||||
|
# Fetch each agent policy once; its package_policies[] already contain both the integration name
|
||||||
|
# and the .package.name, so extract all non-default package names locally in a single jq instead
|
||||||
|
# of re-fetching the same policy per integration.
|
||||||
|
default_packages_json=$(printf '%s\n' "${default_packages[@]}" | jq -R . | jq -s '.')
|
||||||
for AGENT_POLICY in $agent_policies; do
|
for AGENT_POLICY in $agent_policies; do
|
||||||
|
|
||||||
if ! integrations=$(elastic_fleet_integration_policy_names "$AGENT_POLICY"); then
|
if ! policy_json=$(fleet_api "agent_policies/$AGENT_POLICY"); then
|
||||||
# skip the agent policy if we can't get required info, let salt retry. Integrations loaded by this script are non-default integrations.
|
# skip the agent policy if we can't get required info, let salt retry. Integrations loaded by this script are non-default integrations.
|
||||||
echo "Skipping $AGENT_POLICY.. "
|
echo "Skipping $AGENT_POLICY.. "
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
for INTEGRATION in $integrations; do
|
|
||||||
if ! PACKAGE_NAME=$(elastic_fleet_integration_policy_package_name "$AGENT_POLICY" "$INTEGRATION"); then
|
|
||||||
echo "Not adding $INTEGRATION, couldn't get package name"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
# non-default integrations that are in-use in any policy
|
# non-default integrations that are in-use in any policy
|
||||||
if ! [[ " ${default_packages[@]} " =~ " $PACKAGE_NAME " ]]; then
|
while IFS= read -r PACKAGE_NAME; do
|
||||||
in_use_integrations+=("$PACKAGE_NAME")
|
[ -n "$PACKAGE_NAME" ] && in_use_integrations+=("$PACKAGE_NAME")
|
||||||
fi
|
done < <(jq -r --argjson defaults "$default_packages_json" \
|
||||||
done
|
'.item.package_policies[].package.name | select(. as $n | ($defaults | index($n)) | not)' \
|
||||||
|
<<<"$policy_json")
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -f $STATE_FILE_SUCCESS ]]; then
|
if [[ -f $STATE_FILE_SUCCESS ]]; then
|
||||||
@@ -90,72 +66,55 @@ if [[ -f $STATE_FILE_SUCCESS ]]; then
|
|||||||
rm -f $INSTALLED_PACKAGE_LIST
|
rm -f $INSTALLED_PACKAGE_LIST
|
||||||
echo $latest_package_list | jq '{packages: [.items[] | {name: .name, latest_version: .version, installed_version: .installationInfo.version, subscription: .conditions.elastic.subscription }]}' >> $INSTALLED_PACKAGE_LIST
|
echo $latest_package_list | jq '{packages: [.items[] | {name: .name, latest_version: .version, installed_version: .installationInfo.version, subscription: .conditions.elastic.subscription }]}' >> $INSTALLED_PACKAGE_LIST
|
||||||
|
|
||||||
while read -r package; do
|
# Build the bulk install list and the per-package status messages with two jq passes
|
||||||
# get package details
|
# instead of a per-package bash loop. The old loop forked ~10 processes per package
|
||||||
package_name=$(echo "$package" | jq -r '.name')
|
# (5 jq + awk/bc for the version compare) and re-parsed/rewrote a growing JSON file on
|
||||||
latest_version=$(echo "$package" | jq -r '.latest_version')
|
# every add (O(n^2)). Selection and messages below are identical to that logic.
|
||||||
installed_version=$(echo "$package" | jq -r '.installed_version')
|
SUB={% if SUB %}true{% else %}false{% endif %}
|
||||||
subscription=$(echo "$package" | jq -r '.subscription')
|
AUTOUP={% if AUTO_UPGRADE_INTEGRATIONS %}true{% else %}false{% endif %}
|
||||||
bulk_package=$(echo "$package" | jq '{name: .name, version: .latest_version}' )
|
EXCLUDED_JSON=$(printf '%s\n' "${EXCLUDED_INTEGRATIONS[@]}" | jq -R 'select(length>0)' | jq -s '.')
|
||||||
|
INUSE_JSON=$(printf '%s\n' "${in_use_integrations[@]}" | jq -R 'select(length>0)' | jq -s 'unique')
|
||||||
|
|
||||||
if [[ ! "${EXCLUDED_INTEGRATIONS[@]}" =~ "$package_name" ]]; then
|
# vnum replicates the previous version_conversion (%d%03d%03d of the first three dotted
|
||||||
{% if not SUB %}
|
# fields); needs() replicates the excluded/subscription/installed/upgrade/in-use logic.
|
||||||
if [[ "$subscription" != "basic" && "$subscription" != "null" && -n "$subscription" ]]; then
|
JQ_DECISION='
|
||||||
# pass over integrations that require non-basic elastic license
|
def vnum:
|
||||||
echo "$package_name integration requires an Elastic license of $subscription or greater... skipping"
|
[ (split(".")|.[0:3][] | gsub("[^0-9].*";"") | (if .=="" then "0" else . end) | tonumber) ]
|
||||||
continue
|
| (.[0]//0)*1000000 + (.[1]//0)*1000 + (.[2]//0);
|
||||||
else
|
def needs($sub;$autoup;$excluded;$inuse):
|
||||||
if [[ "$installed_version" == "null" || -z "$installed_version" ]]; then
|
.name as $n
|
||||||
echo "$package_name is not installed... Adding to next update."
|
| ($n | IN($excluded[]) | not)
|
||||||
jq --argjson package "$bulk_package" '.packages += [$package]' $BULK_INSTALL_PACKAGE_LIST > $BULK_INSTALL_PACKAGE_TMP && mv $BULK_INSTALL_PACKAGE_TMP $BULK_INSTALL_PACKAGE_LIST
|
and ( $sub or (.subscription==null or .subscription=="basic" or .subscription=="") )
|
||||||
|
and ( (.installed_version==null or .installed_version=="")
|
||||||
|
or ( ((.latest_version|vnum) > (.installed_version|vnum))
|
||||||
|
and ( $autoup or ($n | IN($inuse[]) | not) ) ) );'
|
||||||
|
|
||||||
PENDING_UPDATE=true
|
JQ_ARGS=(--argjson sub "$SUB" --argjson autoup "$AUTOUP" --argjson excluded "$EXCLUDED_JSON" --argjson inuse "$INUSE_JSON")
|
||||||
else
|
|
||||||
results=$(compare_versions "$latest_version" "$installed_version")
|
|
||||||
if [ $results == "greater" ]; then
|
|
||||||
{#- When auto_upgrade_integrations is false, skip upgrading in_use_integrations #}
|
|
||||||
{%- if not AUTO_UPGRADE_INTEGRATIONS %}
|
|
||||||
if ! [[ " ${in_use_integrations[@]} " =~ " $package_name " ]]; then
|
|
||||||
{%- endif %}
|
|
||||||
echo "$package_name is at version $installed_version latest version is $latest_version... Adding to next update."
|
|
||||||
jq --argjson package "$bulk_package" '.packages += [$package]' $BULK_INSTALL_PACKAGE_LIST > $BULK_INSTALL_PACKAGE_TMP && mv $BULK_INSTALL_PACKAGE_TMP $BULK_INSTALL_PACKAGE_LIST
|
|
||||||
|
|
||||||
|
# (a) Per-package status messages (parity with the previous echo output).
|
||||||
|
jq -r "${JQ_ARGS[@]}" "$JQ_DECISION"'
|
||||||
|
.packages[]
|
||||||
|
| .name as $n
|
||||||
|
| if ($n|IN($excluded[])) then "Skipping \($n)..."
|
||||||
|
elif (($sub|not) and (.subscription!=null and .subscription!="basic" and .subscription!="")) then
|
||||||
|
"\($n) integration requires an Elastic license of \(.subscription) or greater... skipping"
|
||||||
|
elif (.installed_version==null or .installed_version=="") then
|
||||||
|
"\($n) is not installed... Adding to next update."
|
||||||
|
elif ((.latest_version|vnum) > (.installed_version|vnum)) then
|
||||||
|
(if ($autoup or ($n|IN($inuse[])|not))
|
||||||
|
then "\($n) is at version \(.installed_version) latest version is \(.latest_version)... Adding to next update."
|
||||||
|
else "skipping available upgrade for in use integration - \($n)." end)
|
||||||
|
else empty end
|
||||||
|
' "$INSTALLED_PACKAGE_LIST"
|
||||||
|
|
||||||
|
# (b) The bulk install list, built in a single pass.
|
||||||
|
jq "${JQ_ARGS[@]}" "$JQ_DECISION"'
|
||||||
|
{packages: [ .packages[] | select(needs($sub;$autoup;$excluded;$inuse)) | {name, version: .latest_version} ]}
|
||||||
|
' "$INSTALLED_PACKAGE_LIST" > "$BULK_INSTALL_PACKAGE_LIST"
|
||||||
|
|
||||||
|
if jq -e '.packages | length > 0' "$BULK_INSTALL_PACKAGE_LIST" >/dev/null; then
|
||||||
PENDING_UPDATE=true
|
PENDING_UPDATE=true
|
||||||
{%- if not AUTO_UPGRADE_INTEGRATIONS %}
|
|
||||||
else
|
|
||||||
echo "skipping available upgrade for in use integration - $package_name."
|
|
||||||
fi
|
fi
|
||||||
{%- endif %}
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
{% else %}
|
|
||||||
if [[ "$installed_version" == "null" || -z "$installed_version" ]]; then
|
|
||||||
echo "$package_name is not installed... Adding to next update."
|
|
||||||
jq --argjson package "$bulk_package" '.packages += [$package]' $BULK_INSTALL_PACKAGE_LIST > $BULK_INSTALL_PACKAGE_TMP && mv $BULK_INSTALL_PACKAGE_TMP $BULK_INSTALL_PACKAGE_LIST
|
|
||||||
PENDING_UPDATE=true
|
|
||||||
else
|
|
||||||
results=$(compare_versions "$latest_version" "$installed_version")
|
|
||||||
if [ $results == "greater" ]; then
|
|
||||||
{#- When auto_upgrade_integrations is false, skip upgrading in_use_integrations #}
|
|
||||||
{%- if not AUTO_UPGRADE_INTEGRATIONS %}
|
|
||||||
if ! [[ " ${in_use_integrations[@]} " =~ " $package_name " ]]; then
|
|
||||||
{%- endif %}
|
|
||||||
echo "$package_name is at version $installed_version latest version is $latest_version... Adding to next update."
|
|
||||||
jq --argjson package "$bulk_package" '.packages += [$package]' $BULK_INSTALL_PACKAGE_LIST > $BULK_INSTALL_PACKAGE_TMP && mv $BULK_INSTALL_PACKAGE_TMP $BULK_INSTALL_PACKAGE_LIST
|
|
||||||
PENDING_UPDATE=true
|
|
||||||
{%- if not AUTO_UPGRADE_INTEGRATIONS %}
|
|
||||||
else
|
|
||||||
echo "skipping available upgrade for in use integration - $package_name."
|
|
||||||
fi
|
|
||||||
{%- endif %}
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
{% endif %}
|
|
||||||
else
|
|
||||||
echo "Skipping $package_name..."
|
|
||||||
fi
|
|
||||||
done <<< "$(jq -c '.packages[]' "$INSTALLED_PACKAGE_LIST")"
|
|
||||||
|
|
||||||
if [ "$PENDING_UPDATE" = true ]; then
|
if [ "$PENDING_UPDATE" = true ]; then
|
||||||
# Run chunked install of packages
|
# Run chunked install of packages
|
||||||
|
|||||||
@@ -9,9 +9,12 @@
|
|||||||
{% from 'elasticsearch/config.map.jinja' import ELASTICSEARCHMERGED %}
|
{% from 'elasticsearch/config.map.jinja' import ELASTICSEARCHMERGED %}
|
||||||
{% from 'elasticsearch/template.map.jinja' import ES_INDEX_SETTINGS, SO_MANAGED_INDICES %}
|
{% from 'elasticsearch/template.map.jinja' import ES_INDEX_SETTINGS, SO_MANAGED_INDICES %}
|
||||||
{% if GLOBALS.role != 'so-heavynode' %}
|
{% if GLOBALS.role != 'so-heavynode' %}
|
||||||
{% from 'elasticsearch/template.map.jinja' import ALL_ADDON_SETTINGS %}
|
{% from 'elasticsearch/template.map.jinja' import ALL_ADDON_SETTINGS, ADDON_INDICES %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
include:
|
||||||
|
- elasticsearch.enabled
|
||||||
|
|
||||||
escomponenttemplates:
|
escomponenttemplates:
|
||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /opt/so/conf/elasticsearch/templates/component
|
- name: /opt/so/conf/elasticsearch/templates/component
|
||||||
@@ -35,6 +38,20 @@ so_index_template_dir:
|
|||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
|
{% if GLOBALS.role != "so-heavynode" %}
|
||||||
|
# Clean up legacy and non-SO managed templates from the elasticsearch/templates/addon-index/ directory
|
||||||
|
addon_index_template_dir:
|
||||||
|
file.directory:
|
||||||
|
- name: /opt/so/conf/elasticsearch/templates/addon-index
|
||||||
|
- clean: True
|
||||||
|
{%- if ADDON_INDICES %}
|
||||||
|
- require:
|
||||||
|
{%- for index in ADDON_INDICES %}
|
||||||
|
- file: addon_index_template_{{index}}
|
||||||
|
{%- endfor %}
|
||||||
|
{%- endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
# Auto-generate index templates for SO managed indices (directly defined in elasticsearch/defaults.yaml)
|
# Auto-generate index templates for SO managed indices (directly defined in elasticsearch/defaults.yaml)
|
||||||
# These index templates are for the core SO datasets and are always required
|
# These index templates are for the core SO datasets and are always required
|
||||||
{% for index, settings in ES_INDEX_SETTINGS.items() %}
|
{% for index, settings in ES_INDEX_SETTINGS.items() %}
|
||||||
|
|||||||
@@ -61,15 +61,25 @@
|
|||||||
{% if ALL_ADDON_SETTINGS_ORIG.keys() | length > 0 %}
|
{% if ALL_ADDON_SETTINGS_ORIG.keys() | length > 0 %}
|
||||||
{% for index in ALL_ADDON_SETTINGS_ORIG.keys() %}
|
{% for index in ALL_ADDON_SETTINGS_ORIG.keys() %}
|
||||||
{% do ALL_ADDON_SETTINGS_GLOBAL_OVERRIDES.update({index: salt['defaults.merge'](ALL_ADDON_SETTINGS_ORIG[index], PILLAR_GLOBAL_OVERRIDES, in_place=False)}) %}
|
{% do ALL_ADDON_SETTINGS_GLOBAL_OVERRIDES.update({index: salt['defaults.merge'](ALL_ADDON_SETTINGS_ORIG[index], PILLAR_GLOBAL_OVERRIDES, in_place=False)}) %}
|
||||||
|
{# Explicitly excluding addon indices from ES_INDEX_SETTINGS_ORIG
|
||||||
|
When manager.soc_managed_annotations runs, new entries are added to the salt/elasticsearch/defaults.yaml file to support 'revert to default' functionality.
|
||||||
|
Subsequent map renders will then incorrectly include 'integration X' in 'ES_INDEX_SETTINGS_ORIG' due to being in the defaults.yaml file. #}
|
||||||
|
{% if index in ES_INDEX_SETTINGS_ORIG.keys() %}
|
||||||
|
{% do ES_INDEX_SETTINGS_ORIG.pop(index) %}
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% set ES_INDEX_SETTINGS = {} %}
|
{% set ES_INDEX_SETTINGS = {} %}
|
||||||
{% macro create_final_index_template(DEFINED_SETTINGS, GLOBAL_OVERRIDES, FINAL_INDEX_SETTINGS) %}
|
{% macro create_final_index_template(DEFINED_SETTINGS, GLOBAL_OVERRIDES, FINAL_INDEX_SETTINGS, EXCLUDE_INDICES=[]) %}
|
||||||
|
|
||||||
{% do GLOBAL_OVERRIDES.update(salt['defaults.merge'](GLOBAL_OVERRIDES, ES_INDEX_PILLAR, in_place=False)) %}
|
{% do GLOBAL_OVERRIDES.update(salt['defaults.merge'](GLOBAL_OVERRIDES, ES_INDEX_PILLAR, in_place=False)) %}
|
||||||
{% for index, settings in GLOBAL_OVERRIDES.items() %}
|
{% for index, settings in GLOBAL_OVERRIDES.items() %}
|
||||||
|
|
||||||
|
{% if index in EXCLUDE_INDICES %}
|
||||||
|
{% continue %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{# prevent this action from being performed on custom defined indices. #}
|
{# prevent this action from being performed on custom defined indices. #}
|
||||||
{# the custom defined index is not present in either of the dictionaries and fails to reder. #}
|
{# the custom defined index is not present in either of the dictionaries and fails to reder. #}
|
||||||
{% if index in DEFINED_SETTINGS and index in GLOBAL_OVERRIDES %}
|
{% if index in DEFINED_SETTINGS and index in GLOBAL_OVERRIDES %}
|
||||||
@@ -150,10 +160,19 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{{ create_final_index_template(ES_INDEX_SETTINGS_ORIG, ES_INDEX_SETTINGS_GLOBAL_OVERRIDES, ES_INDEX_SETTINGS) }}
|
{# Exclude addon integrations from final ES_INDEX_SETTINGS #}
|
||||||
{{ create_final_index_template(ALL_ADDON_SETTINGS_ORIG, ALL_ADDON_SETTINGS_GLOBAL_OVERRIDES, ALL_ADDON_SETTINGS) }}
|
{{ create_final_index_template(ES_INDEX_SETTINGS_ORIG, ES_INDEX_SETTINGS_GLOBAL_OVERRIDES, ES_INDEX_SETTINGS, ALL_ADDON_SETTINGS_ORIG.keys() | list ) }}
|
||||||
|
|
||||||
|
{# Exclude SO managed indices, otherwise ALL_ADDON_SETTINGS will include pillar values
|
||||||
|
of core integrations without merging defaults, resulting in an overlapping, but bad index template being generated. #}
|
||||||
|
{{ create_final_index_template(ALL_ADDON_SETTINGS_ORIG, ALL_ADDON_SETTINGS_GLOBAL_OVERRIDES, ALL_ADDON_SETTINGS, ES_INDEX_SETTINGS_ORIG.keys() | list ) }}
|
||||||
|
|
||||||
{% set SO_MANAGED_INDICES = [] %}
|
{% set SO_MANAGED_INDICES = [] %}
|
||||||
{% for index, settings in ES_INDEX_SETTINGS.items() %}
|
{% for index, settings in ES_INDEX_SETTINGS.items() %}
|
||||||
{% do SO_MANAGED_INDICES.append(index) %}
|
{% do SO_MANAGED_INDICES.append(index) %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
{% set ADDON_INDICES = [] %}
|
||||||
|
{% for index, settings in ALL_ADDON_SETTINGS.items() %}
|
||||||
|
{% do ADDON_INDICES.append(index) %}
|
||||||
|
{% endfor %}
|
||||||
|
|||||||
@@ -11,10 +11,8 @@ ADDON_STATEFILE_SUCCESS=/opt/so/state/addon_estemplates.txt
|
|||||||
ELASTICSEARCH_TEMPLATES_DIR="/opt/so/conf/elasticsearch/templates"
|
ELASTICSEARCH_TEMPLATES_DIR="/opt/so/conf/elasticsearch/templates"
|
||||||
SO_TEMPLATES_DIR="${ELASTICSEARCH_TEMPLATES_DIR}/index"
|
SO_TEMPLATES_DIR="${ELASTICSEARCH_TEMPLATES_DIR}/index"
|
||||||
ADDON_TEMPLATES_DIR="${ELASTICSEARCH_TEMPLATES_DIR}/addon-index"
|
ADDON_TEMPLATES_DIR="${ELASTICSEARCH_TEMPLATES_DIR}/addon-index"
|
||||||
SO_LOAD_FAILURES=0
|
FAILED_NAMES=()
|
||||||
ADDON_LOAD_FAILURES=0
|
FAILED_COUNT=0
|
||||||
SO_LOAD_FAILURES_NAMES=()
|
|
||||||
ADDON_LOAD_FAILURES_NAMES=()
|
|
||||||
IS_HEAVYNODE="false"
|
IS_HEAVYNODE="false"
|
||||||
FORCE="false"
|
FORCE="false"
|
||||||
VERBOSE="false"
|
VERBOSE="false"
|
||||||
@@ -46,20 +44,86 @@ while [[ $# -gt 0 ]]; do
|
|||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Max number of concurrent template PUT jobs. Override via env if needed.
|
||||||
|
MAX_TEMPLATE_JOBS=${MAX_TEMPLATE_JOBS:-10}
|
||||||
|
|
||||||
|
# Block until fewer than MAX_TEMPLATE_JOBS background jobs are running.
|
||||||
|
template_throttle() {
|
||||||
|
while (( $(jobs -rp | wc -l) >= MAX_TEMPLATE_JOBS )); do
|
||||||
|
wait -n
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Per-job failure markers and an output lock for serializing parallel job output.
|
||||||
|
# Each failed load drops one file (named after the template) into FAIL_DIR; the
|
||||||
|
# output of each job is flushed as a single block under flock so concurrent jobs
|
||||||
|
# never interleave their (chatty) retry output.
|
||||||
|
FAIL_DIR=$(mktemp -d)
|
||||||
|
OUTPUT_LOCK="${FAIL_DIR}/.output.lock"
|
||||||
|
: > "$OUTPUT_LOCK"
|
||||||
|
trap 'rm -rf "$FAIL_DIR"' EXIT
|
||||||
|
|
||||||
|
# Record a failure: $1 = the template name/path to report later. Slashes are
|
||||||
|
# encoded so the path becomes a safe single filename.
|
||||||
|
record_failure() {
|
||||||
|
local marker="${1//\//__}"
|
||||||
|
: > "${FAIL_DIR}/fail.${marker}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Populate FAILED_NAMES and FAILED_COUNT from the current phase's markers.
|
||||||
|
# Must run in the current shell (not a command substitution) so the array sticks.
|
||||||
|
collect_failures() {
|
||||||
|
FAILED_NAMES=()
|
||||||
|
FAILED_COUNT=0
|
||||||
|
local f name
|
||||||
|
shopt -s nullglob
|
||||||
|
for f in "${FAIL_DIR}"/fail.*; do
|
||||||
|
name="${f##*/fail.}"
|
||||||
|
name="${name//__//}"
|
||||||
|
FAILED_NAMES+=("$name")
|
||||||
|
FAILED_COUNT=$((FAILED_COUNT + 1))
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clear markers and names between phases so SO and addon counts stay independent.
|
||||||
|
reset_failures() {
|
||||||
|
shopt -s nullglob
|
||||||
|
rm -f "${FAIL_DIR}"/fail.*
|
||||||
|
shopt -u nullglob
|
||||||
|
FAILED_NAMES=()
|
||||||
|
FAILED_COUNT=0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print a block of text atomically (under the shared output lock) so the output
|
||||||
|
# of concurrent background jobs is not interleaved.
|
||||||
|
locked_echo() {
|
||||||
|
{ flock 9; printf '%s\n' "$1"; } 9>>"$OUTPUT_LOCK"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Loads one template file via PUT. Intended to be dispatched as a background job.
|
||||||
|
# $1 uri - e.g. _component_template/foo or _index_template/foo
|
||||||
|
# $2 file - path to the template JSON
|
||||||
|
# $3 report_name - name/path to record if this load fails
|
||||||
load_template() {
|
load_template() {
|
||||||
local uri="$1"
|
local uri="$1"
|
||||||
local file="$2"
|
local file="$2"
|
||||||
|
local report_name="$3"
|
||||||
|
local out rc=0 block
|
||||||
|
|
||||||
echo "Loading template file $file"
|
# Capture everything (including retry's diagnostic chatter) into one block so
|
||||||
if ! output=$(retry 3 3 "so-elasticsearch-query $uri -d@$file -XPUT" "{\"acknowledged\":true}"); then
|
# concurrent jobs never interleave; the whole block is flushed under one flock.
|
||||||
echo "$output"
|
block="Loading template file $file"$'\n'
|
||||||
|
if ! out=$(retry 3 3 "so-elasticsearch-query $uri -d@$file -XPUT" "{\"acknowledged\":true}" 2>&1); then
|
||||||
return 1
|
block+="$out"$'\n'
|
||||||
|
rc=1
|
||||||
elif [[ "$VERBOSE" == "true" ]]; then
|
elif [[ "$VERBOSE" == "true" ]]; then
|
||||||
echo "$output"
|
block+="$out"$'\n'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
{ flock 9; printf '%s' "$block"; } 9>>"$OUTPUT_LOCK"
|
||||||
|
|
||||||
|
(( rc != 0 )) && record_failure "$report_name"
|
||||||
}
|
}
|
||||||
|
|
||||||
check_required_component_template_exists() {
|
check_required_component_template_exists() {
|
||||||
@@ -110,6 +174,9 @@ load_component_templates() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Dispatch loads as throttled background jobs. The barrier (wait) happens in
|
||||||
|
# the caller after all component groups have been dispatched, since index
|
||||||
|
# templates must not load until every component template is in place.
|
||||||
for component in "$pattern"/*.json; do
|
for component in "$pattern"/*.json; do
|
||||||
tmpl_name=$(basename "${component%.json}")
|
tmpl_name=$(basename "${component%.json}")
|
||||||
|
|
||||||
@@ -118,10 +185,8 @@ load_component_templates() {
|
|||||||
tmpl_name="${tmpl_name%-mappings}-mappings"
|
tmpl_name="${tmpl_name%-mappings}-mappings"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! load_template "_component_template/${tmpl_name}" "$component"; then
|
template_throttle
|
||||||
SO_LOAD_FAILURES=$((SO_LOAD_FAILURES + 1))
|
load_template "_component_template/${tmpl_name}" "$component" "$component" &
|
||||||
SO_LOAD_FAILURES_NAMES+=("$component")
|
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,6 +245,9 @@ if [[ "$FORCE" == "true" || ! -f "$SO_STATEFILE_SUCCESS" ]] && index_templates_e
|
|||||||
load_component_templates "Elastic Agent" "elastic-agent"
|
load_component_templates "Elastic Agent" "elastic-agent"
|
||||||
load_component_templates "Security Onion" "so"
|
load_component_templates "Security Onion" "so"
|
||||||
|
|
||||||
|
# Barrier: every component template PUT must complete before we snapshot the
|
||||||
|
# component template list and start loading index templates that depend on them.
|
||||||
|
wait
|
||||||
component_templates=$(so-elasticsearch-component-templates-list)
|
component_templates=$(so-elasticsearch-component-templates-list)
|
||||||
echo -e "Loading Security Onion index templates...\n"
|
echo -e "Loading Security Onion index templates...\n"
|
||||||
for so_idx_tmpl in "${SO_TEMPLATES_DIR}"/*.json; do
|
for so_idx_tmpl in "${SO_TEMPLATES_DIR}"/*.json; do
|
||||||
@@ -189,7 +257,7 @@ if [[ "$FORCE" == "true" || ! -f "$SO_STATEFILE_SUCCESS" ]] && index_templates_e
|
|||||||
# TODO: Better way to load only heavynode specific templates
|
# TODO: Better way to load only heavynode specific templates
|
||||||
if ! check_heavynode_compatiable_index_template "$tmpl_name"; then
|
if ! check_heavynode_compatiable_index_template "$tmpl_name"; then
|
||||||
if [[ "$VERBOSE" == "true" ]]; then
|
if [[ "$VERBOSE" == "true" ]]; then
|
||||||
echo "Skipping over $so_idx_tmpl, template is not a heavynode specific index template."
|
locked_echo "Skipping over $so_idx_tmpl, template is not a heavynode specific index template."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
continue
|
continue
|
||||||
@@ -197,32 +265,34 @@ if [[ "$FORCE" == "true" || ! -f "$SO_STATEFILE_SUCCESS" ]] && index_templates_e
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if check_required_component_template_exists "$so_idx_tmpl"; then
|
if check_required_component_template_exists "$so_idx_tmpl"; then
|
||||||
if ! load_template "_index_template/$tmpl_name" "$so_idx_tmpl"; then
|
template_throttle
|
||||||
SO_LOAD_FAILURES=$((SO_LOAD_FAILURES + 1))
|
load_template "_index_template/$tmpl_name" "$so_idx_tmpl" "$so_idx_tmpl" &
|
||||||
SO_LOAD_FAILURES_NAMES+=("$so_idx_tmpl")
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
echo "Skipping over $so_idx_tmpl due to missing required component template(s)."
|
locked_echo "Skipping over $so_idx_tmpl due to missing required component template(s)."
|
||||||
SO_LOAD_FAILURES=$((SO_LOAD_FAILURES + 1))
|
record_failure "$so_idx_tmpl"
|
||||||
SO_LOAD_FAILURES_NAMES+=("$so_idx_tmpl")
|
|
||||||
|
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ $SO_LOAD_FAILURES -eq 0 ]]; then
|
# Barrier: all SO index template PUTs must finish before tallying failures.
|
||||||
|
wait
|
||||||
|
|
||||||
|
collect_failures
|
||||||
|
if [[ $FAILED_COUNT -eq 0 ]]; then
|
||||||
echo "All Security Onion core templates loaded successfully."
|
echo "All Security Onion core templates loaded successfully."
|
||||||
|
|
||||||
touch "$SO_STATEFILE_SUCCESS"
|
touch "$SO_STATEFILE_SUCCESS"
|
||||||
else
|
else
|
||||||
echo "Encountered $SO_LOAD_FAILURES failure(s) loading templates:"
|
echo "Encountered $FAILED_COUNT failure(s) loading templates:"
|
||||||
for failed_template in "${SO_LOAD_FAILURES_NAMES[@]}"; do
|
for failed_template in "${FAILED_NAMES[@]}"; do
|
||||||
echo " - $failed_template"
|
echo " - $failed_template"
|
||||||
done
|
done
|
||||||
if [[ "$SHOULD_EXIT_ON_FAILURE" == "true" ]]; then
|
if [[ "$SHOULD_EXIT_ON_FAILURE" == "true" ]]; then
|
||||||
fail "Failed to load all Security Onion core templates successfully."
|
fail "Failed to load all Security Onion core templates successfully."
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
reset_failures
|
||||||
elif ! index_templates_exist "$SO_TEMPLATES_DIR"; then
|
elif ! index_templates_exist "$SO_TEMPLATES_DIR"; then
|
||||||
echo "No Security Onion core index templates found in ${SO_TEMPLATES_DIR}, skipping."
|
echo "No Security Onion core index templates found in ${SO_TEMPLATES_DIR}, skipping."
|
||||||
elif [[ -f "$SO_STATEFILE_SUCCESS" ]]; then
|
elif [[ -f "$SO_STATEFILE_SUCCESS" ]]; then
|
||||||
@@ -241,26 +311,27 @@ if should_load_addon_templates; then
|
|||||||
tmpl_name=$(basename "${addon_idx_tmpl%-template.json}")
|
tmpl_name=$(basename "${addon_idx_tmpl%-template.json}")
|
||||||
|
|
||||||
if check_required_component_template_exists "$addon_idx_tmpl"; then
|
if check_required_component_template_exists "$addon_idx_tmpl"; then
|
||||||
if ! load_template "_index_template/${tmpl_name}" "$addon_idx_tmpl"; then
|
template_throttle
|
||||||
ADDON_LOAD_FAILURES=$((ADDON_LOAD_FAILURES + 1))
|
load_template "_index_template/${tmpl_name}" "$addon_idx_tmpl" "$addon_idx_tmpl" &
|
||||||
ADDON_LOAD_FAILURES_NAMES+=("$addon_idx_tmpl")
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
echo "Skipping over $addon_idx_tmpl due to missing required component template(s)."
|
locked_echo "Skipping over $addon_idx_tmpl due to missing required component template(s)."
|
||||||
ADDON_LOAD_FAILURES=$((ADDON_LOAD_FAILURES + 1))
|
record_failure "$addon_idx_tmpl"
|
||||||
ADDON_LOAD_FAILURES_NAMES+=("$addon_idx_tmpl")
|
|
||||||
|
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ $ADDON_LOAD_FAILURES -eq 0 ]]; then
|
# Barrier: all addon index template PUTs must finish before tallying failures.
|
||||||
|
wait
|
||||||
|
|
||||||
|
collect_failures
|
||||||
|
if [[ $FAILED_COUNT -eq 0 ]]; then
|
||||||
echo "All addon integration templates loaded successfully."
|
echo "All addon integration templates loaded successfully."
|
||||||
|
|
||||||
touch "$ADDON_STATEFILE_SUCCESS"
|
touch "$ADDON_STATEFILE_SUCCESS"
|
||||||
else
|
else
|
||||||
echo "Encountered $ADDON_LOAD_FAILURES failure(s) loading addon integration templates:"
|
echo "Encountered $FAILED_COUNT failure(s) loading addon integration templates:"
|
||||||
for failed_template in "${ADDON_LOAD_FAILURES_NAMES[@]}"; do
|
for failed_template in "${FAILED_NAMES[@]}"; do
|
||||||
echo " - $failed_template"
|
echo " - $failed_template"
|
||||||
done
|
done
|
||||||
if [[ "$SHOULD_EXIT_ON_FAILURE" == "true" ]]; then
|
if [[ "$SHOULD_EXIT_ON_FAILURE" == "true" ]]; then
|
||||||
|
|||||||
@@ -6,6 +6,37 @@
|
|||||||
|
|
||||||
. /usr/sbin/so-common
|
. /usr/sbin/so-common
|
||||||
|
|
||||||
|
MAX_JOBS=10
|
||||||
|
|
||||||
|
# Lock used to serialize block writes so concurrent jobs never interleave their output.
|
||||||
|
ILM_OUTPUT_LOCK=$(mktemp)
|
||||||
|
trap 'rm -f "$ILM_OUTPUT_LOCK"' EXIT
|
||||||
|
|
||||||
|
# Policies are loaded concurrently (up to MAX_JOBS at a time) for speed. Each policy's block is
|
||||||
|
# printed the moment its curl returns, so output appears in COMPLETION ORDER, not the order
|
||||||
|
# policies are defined in configuration.
|
||||||
|
echo "Loading ILM policies concurrently; output below appears in completion order, not configuration order."
|
||||||
|
echo
|
||||||
|
|
||||||
|
put_policy() {
|
||||||
|
local desc="$1" policyname="$2" data="$3" result
|
||||||
|
result=$(curl -K /opt/so/conf/elasticsearch/curl.config -s -k -L \
|
||||||
|
-X PUT "https://localhost:9200/_ilm/policy/${policyname}" \
|
||||||
|
-H 'Content-Type: application/json' -d"${data}")
|
||||||
|
# curl above ran in parallel; serialize just this block write so concurrent jobs never interleave.
|
||||||
|
{
|
||||||
|
flock 200
|
||||||
|
printf 'Setting up %s policy...\n%s\n\n' "${desc}" "${result}"
|
||||||
|
} 200>>"${ILM_OUTPUT_LOCK}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Block until fewer than MAX_JOBS background curls are running.
|
||||||
|
throttle() {
|
||||||
|
while (( $(jobs -rp | wc -l) >= MAX_JOBS )); do
|
||||||
|
wait -n
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
{%- from 'elasticsearch/template.map.jinja' import ES_INDEX_SETTINGS %}
|
{%- from 'elasticsearch/template.map.jinja' import ES_INDEX_SETTINGS %}
|
||||||
{%- if GLOBALS.role != "so-heavynode" %}
|
{%- if GLOBALS.role != "so-heavynode" %}
|
||||||
{%- from 'elasticsearch/template.map.jinja' import ALL_ADDON_SETTINGS %}
|
{%- from 'elasticsearch/template.map.jinja' import ALL_ADDON_SETTINGS %}
|
||||||
@@ -14,35 +45,26 @@
|
|||||||
{%- for index, settings in ES_INDEX_SETTINGS.items() %}
|
{%- for index, settings in ES_INDEX_SETTINGS.items() %}
|
||||||
{%- if settings.policy is defined %}
|
{%- if settings.policy is defined %}
|
||||||
{%- if index == 'so-logs-detections.alerts' %}
|
{%- if index == 'so-logs-detections.alerts' %}
|
||||||
echo
|
throttle
|
||||||
echo "Setting up so-logs-detections.alerts-so policy..."
|
put_policy "so-logs-detections.alerts-so" "{{ index }}-so" '{ "policy": {{ settings.policy | tojson(true) }} }' &
|
||||||
curl -K /opt/so/conf/elasticsearch/curl.config -b "sid=$SESSIONCOOKIE" -s -k -L -X PUT "https://localhost:9200/_ilm/policy/{{ index }}-so" -H 'Content-Type: application/json' -d'{ "policy": {{ settings.policy | tojson(true) }} }'
|
|
||||||
echo
|
|
||||||
{%- elif index == 'so-logs-soc' %}
|
{%- elif index == 'so-logs-soc' %}
|
||||||
echo
|
throttle
|
||||||
echo "Setting up so-soc-logs policy..."
|
put_policy "so-soc-logs" "so-soc-logs" '{ "policy": {{ settings.policy | tojson(true) }} }' &
|
||||||
curl -K /opt/so/conf/elasticsearch/curl.config -b "sid=$SESSIONCOOKIE" -s -k -L -X PUT "https://localhost:9200/_ilm/policy/so-soc-logs" -H 'Content-Type: application/json' -d'{ "policy": {{ settings.policy | tojson(true) }} }'
|
throttle
|
||||||
echo
|
put_policy "{{ index }}-logs" "{{ index }}-logs" '{ "policy": {{ settings.policy | tojson(true) }} }' &
|
||||||
echo
|
|
||||||
echo "Setting up {{ index }}-logs policy..."
|
|
||||||
curl -K /opt/so/conf/elasticsearch/curl.config -b "sid=$SESSIONCOOKIE" -s -k -L -X PUT "https://localhost:9200/_ilm/policy/{{ index }}-logs" -H 'Content-Type: application/json' -d'{ "policy": {{ settings.policy | tojson(true) }} }'
|
|
||||||
echo
|
|
||||||
{%- else %}
|
{%- else %}
|
||||||
echo
|
throttle
|
||||||
echo "Setting up {{ index }}-logs policy..."
|
put_policy "{{ index }}-logs" "{{ index }}-logs" '{ "policy": {{ settings.policy | tojson(true) }} }' &
|
||||||
curl -K /opt/so/conf/elasticsearch/curl.config -b "sid=$SESSIONCOOKIE" -s -k -L -X PUT "https://localhost:9200/_ilm/policy/{{ index }}-logs" -H 'Content-Type: application/json' -d'{ "policy": {{ settings.policy | tojson(true) }} }'
|
|
||||||
echo
|
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
echo
|
|
||||||
{%- if GLOBALS.role != "so-heavynode" %}
|
{%- if GLOBALS.role != "so-heavynode" %}
|
||||||
{%- for index, settings in ALL_ADDON_SETTINGS.items() %}
|
{%- for index, settings in ALL_ADDON_SETTINGS.items() %}
|
||||||
{%- if settings.policy is defined %}
|
{%- if settings.policy is defined %}
|
||||||
echo
|
throttle
|
||||||
echo "Setting up {{ index }}-logs policy..."
|
put_policy "{{ index }}-logs" "{{ index }}-logs" '{ "policy": {{ settings.policy | tojson(true) }} }' &
|
||||||
curl -K /opt/so/conf/elasticsearch/curl.config -b "sid=$SESSIONCOOKIE" -s -k -L -X PUT "https://localhost:9200/_ilm/policy/{{ index }}-logs" -H 'Content-Type: application/json' -d'{ "policy": {{ settings.policy | tojson(true) }} }'
|
|
||||||
echo
|
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
|
wait
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ kratos:
|
|||||||
config:
|
config:
|
||||||
session:
|
session:
|
||||||
lifespan:
|
lifespan:
|
||||||
description: Defines the length of a login session.
|
description: Defines the length of a login session before it will timeout, and require a new login.
|
||||||
global: True
|
global: True
|
||||||
helpLink: kratos
|
helpLink: kratos
|
||||||
whoami:
|
whoami:
|
||||||
|
|||||||
@@ -31,11 +31,13 @@ sync_es_users:
|
|||||||
- http: wait_for_kratos
|
- http: wait_for_kratos
|
||||||
- file: so-user.lock # require so-user.lock file to be missing
|
- file: so-user.lock # require so-user.lock file to be missing
|
||||||
|
|
||||||
# we dont want this added too early in setup, so we add the onlyif to verify 'startup_states: highstate'
|
# we dont want this added too early in setup, so the onlyif gates on the
|
||||||
# is in the minion config. That line is added before the final highstate during setup
|
# /opt/so/state/setup-complete marker. The marker is written by
|
||||||
|
# mark_setup_complete in setup/so-functions just before the final setup
|
||||||
|
# highstate (and by an upgrade-path state for systems set up under the old gate).
|
||||||
so-user_sync:
|
so-user_sync:
|
||||||
cron.present:
|
cron.present:
|
||||||
- user: root
|
- user: root
|
||||||
- name: 'PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin /usr/sbin/so-user sync &>> /opt/so/log/soc/sync.log'
|
- name: 'PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin /usr/sbin/so-user sync &>> /opt/so/log/soc/sync.log'
|
||||||
- identifier: so-user_sync
|
- identifier: so-user_sync
|
||||||
- onlyif: "grep -x 'startup_states: highstate' /etc/salt/minion"
|
- onlyif: "test -e /opt/so/state/setup-complete"
|
||||||
|
|||||||
Executable
+117
@@ -0,0 +1,117 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
# Runs once per boot on managers (via so-boot-mine-update.service), before
|
||||||
|
# so-boot-highstate.service. Waits for the responsive minion set to settle, pushes
|
||||||
|
# mine.update, waits until every up minion has actually reported to the mine, then
|
||||||
|
# warms the master's per-minion pillar cache so the mine-backed node pillars (node
|
||||||
|
# IPs, ES/Redis/Logstash/hypervisor discovery -- some glob- and some pillar/grain-
|
||||||
|
# targeted) are complete before the boot highstate renders them. Otherwise a node
|
||||||
|
# that is up but not yet fully reported gets dropped from those pillars and torn
|
||||||
|
# out of the configs they build (e.g. so-elasticsearch ExtraHosts -> container recreate).
|
||||||
|
|
||||||
|
MAX_WAIT=${MINE_UPDATE_MAX_WAIT:-180} # hard backstop only
|
||||||
|
INTERVAL=10
|
||||||
|
STABLE_CHECKS=3 # up-count must hold steady this many polls
|
||||||
|
elapsed=0
|
||||||
|
prev=-1
|
||||||
|
stable=0
|
||||||
|
up=0
|
||||||
|
|
||||||
|
# Wait for the *reachable* minion set to settle rather than for every accepted
|
||||||
|
# key to report up: an operator may accept a minion's key and then intentionally
|
||||||
|
# power off that host, so requiring up >= accepted would never be satisfied and
|
||||||
|
# we'd always burn the full MAX_WAIT. Once the responsive count stops growing we
|
||||||
|
# stop waiting and run mine.update against whoever is up.
|
||||||
|
while [ "$elapsed" -lt "$MAX_WAIT" ]; do
|
||||||
|
up=$(/usr/bin/salt-run manage.up --out=json 2>/dev/null \
|
||||||
|
| python3 -c 'import sys,json; print(len(json.load(sys.stdin)))' 2>/dev/null)
|
||||||
|
up=${up:-0}
|
||||||
|
if [ "$up" -gt 0 ] && [ "$up" -eq "$prev" ]; then
|
||||||
|
stable=$((stable + 1))
|
||||||
|
[ "$stable" -ge "$STABLE_CHECKS" ] && break
|
||||||
|
else
|
||||||
|
stable=0
|
||||||
|
fi
|
||||||
|
prev=$up
|
||||||
|
sleep "$INTERVAL"
|
||||||
|
elapsed=$((elapsed + INTERVAL))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "so-boot-mine-update: ${up} minions up (settled after ${elapsed}s); running mine.update"
|
||||||
|
/usr/bin/salt '*' mine.update --out=txt
|
||||||
|
|
||||||
|
# A node that is up but has not yet re-reported network.ip_addrs to the mine is
|
||||||
|
# silently dropped from mine-backed pillars (elasticsearch:nodes, node_data, ...)
|
||||||
|
# when highstate recompiles them -- which e.g. removes it from so-elasticsearch
|
||||||
|
# ExtraHosts and forces a container recreate. After the broad mine.update above,
|
||||||
|
# wait until every up minion actually has network.ip_addrs in the mine, re-pushing
|
||||||
|
# mine.update to stragglers, before releasing the boot highstate. Bounded by the
|
||||||
|
# same MAX_WAIT backstop so a slow/down node never blocks boot indefinitely.
|
||||||
|
missing=""
|
||||||
|
while [ "$elapsed" -lt "$MAX_WAIT" ]; do
|
||||||
|
up_json=$(/usr/bin/salt-run manage.up --out=json 2>/dev/null)
|
||||||
|
mine_json=$(/usr/bin/salt-run mine.get '*' network.ip_addrs tgt_type=glob --out=json 2>/dev/null)
|
||||||
|
missing=$(printf '%s' "$up_json" | python3 -c '
|
||||||
|
import sys, json
|
||||||
|
up = set(json.load(sys.stdin) or [])
|
||||||
|
mine = {k for k, v in (json.loads(sys.argv[1]) or {}).items() if v}
|
||||||
|
print("\n".join(sorted(up - mine)))
|
||||||
|
' "$mine_json" 2>/dev/null)
|
||||||
|
if [ -z "$missing" ]; then
|
||||||
|
echo "so-boot-mine-update: mine complete for all up minions after ${elapsed}s"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "so-boot-mine-update: mine missing up minion(s): $(echo $missing); re-running mine.update"
|
||||||
|
for m in $missing; do /usr/bin/salt "$m" mine.update --out=txt; done
|
||||||
|
sleep "$INTERVAL"
|
||||||
|
elapsed=$((elapsed + INTERVAL))
|
||||||
|
done
|
||||||
|
[ -n "$missing" ] && echo "so-boot-mine-update: WARNING ${MAX_WAIT}s backstop hit; up minion(s) still absent from mine: $(echo $missing); highstate may drop them from configs"
|
||||||
|
|
||||||
|
# The pillar/compound-targeted node pillars (elasticsearch:nodes, redis:nodes,
|
||||||
|
# logstash:nodes, hypervisor:nodes) resolve their target against the master's
|
||||||
|
# per-minion data cache (grains+pillar in .../minions/<id>/data.p), populated only
|
||||||
|
# when a minion's pillar is (re)compiled -- separately from the mine. A freshly
|
||||||
|
# booted node can be in the mine (glob/node_data sees it) yet absent from that
|
||||||
|
# cache, so it is dropped from those pillars and from the configs they build (e.g.
|
||||||
|
# so-elasticsearch ExtraHosts). Force a synchronous pillar refresh so the master
|
||||||
|
# caches every up node's pillar; refresh_pillar wait=True returns only once the
|
||||||
|
# pillar is recompiled (and thus cached for matching). Retry stragglers <= MAX_WAIT.
|
||||||
|
echo "so-boot-mine-update: warming master pillar cache for pillar/grain-targeted node pillars"
|
||||||
|
/usr/bin/salt '*' saltutil.refresh_pillar wait=True --out=txt
|
||||||
|
missing=""
|
||||||
|
while [ "$elapsed" -lt "$MAX_WAIT" ]; do
|
||||||
|
up_json=$(/usr/bin/salt-run manage.up --out=json 2>/dev/null)
|
||||||
|
cached_json=$(/usr/bin/salt-run cache.pillar tgt='*' --out=json 2>/dev/null)
|
||||||
|
missing=$(printf '%s' "$up_json" | python3 -c '
|
||||||
|
import sys, json
|
||||||
|
up = set(json.load(sys.stdin) or [])
|
||||||
|
cached = {k for k, v in (json.loads(sys.argv[1]) or {}).items() if v}
|
||||||
|
print("\n".join(sorted(up - cached)))
|
||||||
|
' "$cached_json" 2>/dev/null)
|
||||||
|
if [ -z "$missing" ]; then
|
||||||
|
echo "so-boot-mine-update: pillar cache warm for all up minions after ${elapsed}s"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "so-boot-mine-update: pillar not yet cached for: $(echo $missing); refreshing"
|
||||||
|
for m in $missing; do /usr/bin/salt "$m" saltutil.refresh_pillar wait=True --out=txt; done
|
||||||
|
sleep "$INTERVAL"
|
||||||
|
elapsed=$((elapsed + INTERVAL))
|
||||||
|
done
|
||||||
|
[ -n "$missing" ] && echo "so-boot-mine-update: WARNING ${MAX_WAIT}s backstop hit; pillar not cached for: $(echo $missing); pillar-targeted pillars may drop them"
|
||||||
|
|
||||||
|
# Log what the mine-backed pillars render so the boot-time state is inspectable.
|
||||||
|
/usr/bin/salt-call saltutil.refresh_pillar >/dev/null 2>&1
|
||||||
|
sleep 2
|
||||||
|
for key in node_data elasticsearch:nodes; do
|
||||||
|
rendered=$(/usr/bin/salt-call --out=json pillar.get "$key" 2>/dev/null \
|
||||||
|
| python3 -c 'import sys,json; print(json.dumps(json.load(sys.stdin).get("local"), indent=2, sort_keys=True))' 2>/dev/null)
|
||||||
|
echo "so-boot-mine-update: ${key} rendered as:"
|
||||||
|
echo "${rendered:-null}"
|
||||||
|
done
|
||||||
|
exit 0
|
||||||
+266
-21
@@ -188,13 +188,6 @@ airgap_update_dockers() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
backup_old_states_pillars() {
|
|
||||||
|
|
||||||
tar czf /nsm/backup/$(echo $INSTALLEDVERSION)_$(date +%Y%m%d-%H%M%S)_soup_default_states_pillars.tar.gz /opt/so/saltstack/default/
|
|
||||||
tar czf /nsm/backup/$(echo $INSTALLEDVERSION)_$(date +%Y%m%d-%H%M%S)_soup_local_states_pillars.tar.gz /opt/so/saltstack/local/
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
update_registry() {
|
update_registry() {
|
||||||
docker stop so-dockerregistry
|
docker stop so-dockerregistry
|
||||||
docker rm so-dockerregistry
|
docker rm so-dockerregistry
|
||||||
@@ -350,10 +343,11 @@ highstate() {
|
|||||||
masterlock() {
|
masterlock() {
|
||||||
echo "Locking Salt Master"
|
echo "Locking Salt Master"
|
||||||
mv -v $TOPFILE $BACKUPTOPFILE
|
mv -v $TOPFILE $BACKUPTOPFILE
|
||||||
echo "base:" > $TOPFILE
|
# Render the real top file only for the host running soup; every other
|
||||||
echo " $MINIONID:" >> $TOPFILE
|
# minion gets an empty top (no states) while the master is upgrading.
|
||||||
echo " - ca" >> $TOPFILE
|
echo "{% if grains['id'] == '$MINIONID' %}" > $TOPFILE
|
||||||
echo " - elasticsearch" >> $TOPFILE
|
cat $BACKUPTOPFILE >> $TOPFILE
|
||||||
|
echo "{% endif %}" >> $TOPFILE
|
||||||
}
|
}
|
||||||
|
|
||||||
masterunlock() {
|
masterunlock() {
|
||||||
@@ -372,6 +366,7 @@ preupgrade_changes() {
|
|||||||
|
|
||||||
[[ "$INSTALLEDVERSION" =~ ^2\.4\.21[0-9]+$ ]] && up_to_3.0.0
|
[[ "$INSTALLEDVERSION" =~ ^2\.4\.21[0-9]+$ ]] && up_to_3.0.0
|
||||||
[[ "$INSTALLEDVERSION" == "3.0.0" ]] && up_to_3.1.0
|
[[ "$INSTALLEDVERSION" == "3.0.0" ]] && up_to_3.1.0
|
||||||
|
[[ "$INSTALLEDVERSION" == "3.1.0" ]] && up_to_3.2.0
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,6 +376,7 @@ postupgrade_changes() {
|
|||||||
|
|
||||||
[[ "$POSTVERSION" =~ ^2\.4\.21[0-9]+$ ]] && post_to_3.0.0
|
[[ "$POSTVERSION" =~ ^2\.4\.21[0-9]+$ ]] && post_to_3.0.0
|
||||||
[[ "$POSTVERSION" == "3.0.0" ]] && post_to_3.1.0
|
[[ "$POSTVERSION" == "3.0.0" ]] && post_to_3.1.0
|
||||||
|
[[ "$POSTVERSION" == "3.1.0" ]] && post_to_3.2.0
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,6 +529,23 @@ elasticfleet_set_agent_logging_level_warn() {
|
|||||||
done <<< "$policies_to_update"
|
done <<< "$policies_to_update"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_logstash_pipeline_name() {
|
||||||
|
local original_pipeline_name="$1"
|
||||||
|
local new_pipeline_name="$2"
|
||||||
|
|
||||||
|
echo "Checking for conflicting logstash defined_pipelines pillar value."
|
||||||
|
local LOGSTASH_FILE=/opt/so/saltstack/local/pillar/logstash/soc_logstash.sls
|
||||||
|
local MINIONDIR=/opt/so/saltstack/local/pillar/minions
|
||||||
|
for pillar_file in "$LOGSTASH_FILE" "$MINIONDIR"/*.sls; do
|
||||||
|
[[ -f "$pillar_file" ]] || continue
|
||||||
|
if grep -q "$original_pipeline_name$" "$pillar_file"; then
|
||||||
|
echo "Found conflicting defined_pipeline pillar value in $pillar_file. Updating to use the new logstash pipeline name."
|
||||||
|
sed -i "s#$original_pipeline_name\$#$new_pipeline_name#g" "$pillar_file"
|
||||||
|
chown socore:socore "$pillar_file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
check_transform_health_and_reauthorize() {
|
check_transform_health_and_reauthorize() {
|
||||||
. /usr/sbin/so-elastic-fleet-common
|
. /usr/sbin/so-elastic-fleet-common
|
||||||
|
|
||||||
@@ -676,6 +689,10 @@ rename_strelka_scan_lnk() {
|
|||||||
rm -f "$TMP_VALUE_FILE"
|
rm -f "$TMP_VALUE_FILE"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fix_logstash_0013_lumberjack_pipeline_name() {
|
||||||
|
update_logstash_pipeline_name "so/0013_input_lumberjack_fleet.conf" "so/0013_input_lumberjack_fleet.conf.jinja"
|
||||||
|
}
|
||||||
|
|
||||||
up_to_3.1.0() {
|
up_to_3.1.0() {
|
||||||
ensure_postgres_local_pillar
|
ensure_postgres_local_pillar
|
||||||
ensure_postgres_secret
|
ensure_postgres_secret
|
||||||
@@ -684,6 +701,7 @@ up_to_3.1.0() {
|
|||||||
# Clear existing component template state file.
|
# Clear existing component template state file.
|
||||||
rm -f /opt/so/state/esfleet_component_templates.json
|
rm -f /opt/so/state/esfleet_component_templates.json
|
||||||
rename_strelka_scan_lnk
|
rename_strelka_scan_lnk
|
||||||
|
fix_logstash_0013_lumberjack_pipeline_name
|
||||||
|
|
||||||
INSTALLEDVERSION=3.1.0
|
INSTALLEDVERSION=3.1.0
|
||||||
}
|
}
|
||||||
@@ -720,6 +738,48 @@ post_to_3.1.0() {
|
|||||||
|
|
||||||
### 3.1.0 End ###
|
### 3.1.0 End ###
|
||||||
|
|
||||||
|
### 3.2.0 Scripts ###
|
||||||
|
|
||||||
|
bootstrap_so_soc_database() {
|
||||||
|
# init-db.sh is mounted into so-postgres at /docker-entrypoint-initdb.d/init-db.sh
|
||||||
|
# and runs automatically only on a fresh data directory. Hosts upgrading from
|
||||||
|
# 3.1.0 already have /nsm/postgres populated, so the so_soc bootstrap block
|
||||||
|
# added in 3.2 never fires. Re-run the script explicitly; it's idempotent.
|
||||||
|
echo "Bootstrapping so_soc database via init-db.sh."
|
||||||
|
# The postgres image has no USER directive, so `docker exec` defaults to
|
||||||
|
# root, and the container env intentionally omits POSTGRES_USER (the upstream
|
||||||
|
# entrypoint defaults it transiently during first-init only). Recreate both
|
||||||
|
# so psql inside init-db.sh resolves the connect user correctly.
|
||||||
|
local exec_cmd="docker exec -u postgres -e POSTGRES_USER=postgres so-postgres bash /docker-entrypoint-initdb.d/init-db.sh"
|
||||||
|
if ! /usr/sbin/so-postgres-wait; then
|
||||||
|
FINAL_MESSAGE_QUEUE+=("WARNING: so-postgres was not ready during the 3.2.0 upgrade; the so_soc database may not have been bootstrapped. Re-run manually: $exec_cmd")
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if ! $exec_cmd; then
|
||||||
|
FINAL_MESSAGE_QUEUE+=("WARNING: init-db.sh failed inside so-postgres during the 3.2.0 upgrade; the so_soc database may not have been bootstrapped. Re-run manually: $exec_cmd")
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "so_soc bootstrap complete."
|
||||||
|
}
|
||||||
|
|
||||||
|
up_to_3.2.0() {
|
||||||
|
fix_logstash_0013_lumberjack_pipeline_name
|
||||||
|
|
||||||
|
INSTALLEDVERSION=3.2.0
|
||||||
|
}
|
||||||
|
|
||||||
|
post_to_3.2.0() {
|
||||||
|
bootstrap_so_soc_database
|
||||||
|
|
||||||
|
# Including agent regen script here since it was missed in post_to_3.1.0
|
||||||
|
echo "Regenerating Elastic Agent Installers"
|
||||||
|
/sbin/so-elastic-agent-gen-installers
|
||||||
|
|
||||||
|
POSTVERSION=3.2.0
|
||||||
|
}
|
||||||
|
|
||||||
|
### 3.2.0 End ###
|
||||||
|
|
||||||
|
|
||||||
repo_sync() {
|
repo_sync() {
|
||||||
echo "Sync the local repo."
|
echo "Sync the local repo."
|
||||||
@@ -971,6 +1031,9 @@ verify_es_version_compatibility() {
|
|||||||
local is_active_intermediate_upgrade=1
|
local is_active_intermediate_upgrade=1
|
||||||
# supported upgrade paths for SO-ES versions
|
# supported upgrade paths for SO-ES versions
|
||||||
declare -A es_upgrade_map=(
|
declare -A es_upgrade_map=(
|
||||||
|
["8.18.4"]="8.18.6 8.18.8 9.0.8"
|
||||||
|
["8.18.6"]="8.18.8 9.0.8"
|
||||||
|
["8.18.8"]="9.0.8"
|
||||||
["9.0.8"]="9.3.3"
|
["9.0.8"]="9.3.3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -994,6 +1057,171 @@ verify_es_version_compatibility() {
|
|||||||
exit 160
|
exit 160
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
compatible_es_versions="$target_es_version"
|
||||||
|
for current_version in "${!es_upgrade_map[@]}"; do
|
||||||
|
# shellcheck disable=SC2076
|
||||||
|
if [[ " ${es_upgrade_map[$current_version]} " =~ " $target_es_version " ]]; then
|
||||||
|
compatible_es_versions+=" $current_version"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if the given ES version can directly upgrade to the target ES version. Used to assist with catching lagging nodes during the upgrade process
|
||||||
|
es_version_can_upgrade_to_target() {
|
||||||
|
local current_version="$1"
|
||||||
|
# shellcheck disable=SC2076
|
||||||
|
if [[ -n "$current_version" && " $compatible_es_versions " =~ " $current_version " ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Gather Elasticsearch cluster version info and verify that each node in the cluster is running a version compatible with the target ES version.
|
||||||
|
verify_searchnodes_es_target_compatibility() {
|
||||||
|
local retries=20
|
||||||
|
local retry_count=0
|
||||||
|
local delay=180
|
||||||
|
local expected_es_nodes searchnode_minions attempt
|
||||||
|
local searchnode_discovery_success=false
|
||||||
|
SEARCHNODE_ES_VERSIONS=""
|
||||||
|
|
||||||
|
for attempt in {1..3}; do
|
||||||
|
if searchnode_minions=$(set -o pipefail; salt-key --out=json --list=accepted 2> /dev/null | jq -r '.minions[]? | select(endswith("searchnode"))'); then
|
||||||
|
searchnode_discovery_success=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Failed to retrieve grid searchnodes via salt-key... Retrying in 30 seconds. Attempt $attempt of 3."
|
||||||
|
sleep 30
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$searchnode_discovery_success" != "true" ]]; then
|
||||||
|
echo "Failed to retrieve grid searchnodes via salt-key."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Always add node running soup to expected es nodes
|
||||||
|
expected_es_nodes="${MINIONID%_*}"
|
||||||
|
while IFS= read -r searchnode_minion; do
|
||||||
|
[[ -z "$searchnode_minion" ]] && continue
|
||||||
|
expected_es_nodes+=$'\n'"${searchnode_minion%_searchnode}"
|
||||||
|
done <<< "$searchnode_minions"
|
||||||
|
|
||||||
|
while [[ $retry_count -lt $retries ]]; do
|
||||||
|
SEARCHNODE_ES_VERSIONS=$(so-elasticsearch-query _nodes/_all/version --retry 5 --retry-delay 10 --fail 2>&1)
|
||||||
|
local exit_status=$?
|
||||||
|
|
||||||
|
if [[ $exit_status -ne 0 ]]; then
|
||||||
|
echo "Failed to retrieve Elasticsearch versions from searchnodes... Retrying in $delay seconds. Attempt $((retry_count + 1)) of $retries."
|
||||||
|
((retry_count++))
|
||||||
|
sleep $delay
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
local all_searchnodes_compatible=true
|
||||||
|
while IFS=$'\t' read -r node current_version; do
|
||||||
|
[[ -z "$node" ]] && continue
|
||||||
|
if ! es_version_can_upgrade_to_target "$current_version"; then
|
||||||
|
echo "Searchnode $node is running Elasticsearch $current_version, which is not directly upgradable to Elasticsearch $target_es_version."
|
||||||
|
all_searchnodes_compatible=false
|
||||||
|
fi
|
||||||
|
done < <(echo "$SEARCHNODE_ES_VERSIONS" | jq -r '.nodes | to_entries[] | [.value.name, .value.version] | @tsv')
|
||||||
|
|
||||||
|
while IFS= read -r expected_es_node; do
|
||||||
|
[[ -z "$expected_es_node" ]] && continue
|
||||||
|
if ! echo "$SEARCHNODE_ES_VERSIONS" | jq -e --arg node "$expected_es_node" '.nodes | to_entries | any(.value.name == $node)' > /dev/null; then
|
||||||
|
echo "Searchnode $expected_es_node did not report an Elasticsearch version. It may be offline or still upgrading."
|
||||||
|
all_searchnodes_compatible=false
|
||||||
|
fi
|
||||||
|
done <<< "$expected_es_nodes"
|
||||||
|
|
||||||
|
if [[ "$all_searchnodes_compatible" == true ]]; then
|
||||||
|
echo "All Searchnodes are upgradable to Elasticsearch $target_es_version."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "One or more Searchnodes cannot upgrade directly to Elasticsearch $target_es_version. Rechecking in $delay seconds. Attempt $((retry_count + 1)) of $retries."
|
||||||
|
((retry_count++))
|
||||||
|
sleep $delay
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Gather heavynode version info and verify that each node is running a version compatible with the target ES version.
|
||||||
|
verify_heavynodes_es_target_compatibility() {
|
||||||
|
local heavynode_minions attempt
|
||||||
|
local retries=20
|
||||||
|
local retry_count=0
|
||||||
|
local delay=180
|
||||||
|
local heavynode_discovery_success=false
|
||||||
|
HEAVYNODE_ES_VERSIONS=""
|
||||||
|
|
||||||
|
for attempt in {1..3}; do
|
||||||
|
if heavynode_minions=$(set -o pipefail; salt-key --out=json --list=accepted 2> /dev/null | jq -r '.minions[]? | select(endswith("heavynode"))'); then
|
||||||
|
heavynode_discovery_success=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Failed to retrieve grid heavynodes via salt-key... Retrying in 30 seconds. Attempt $attempt of 3."
|
||||||
|
sleep 30
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$heavynode_discovery_success" != "true" ]]; then
|
||||||
|
echo "Failed to retrieve grid heavynodes via salt-key."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$heavynode_minions" ]]; then
|
||||||
|
echo "No heavynodes detected. Skipping heavynode Elasticsearch version compatibility check."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
while [[ $retry_count -lt $retries ]]; do
|
||||||
|
HEAVYNODE_ES_VERSIONS=$(salt -C 'G@role:so-heavynode' cmd.run 'set -o pipefail; so-elasticsearch-query / --retry 5 --retry-delay 10 | jq -er ".version.number"' shell=/bin/bash --out=json 2> /dev/null)
|
||||||
|
local exit_status=$?
|
||||||
|
|
||||||
|
if [[ $exit_status -ne 0 ]]; then
|
||||||
|
echo "Failed to retrieve Elasticsearch version from one or more heavynodes... Retrying in $delay seconds. Attempt $((retry_count + 1)) of $retries."
|
||||||
|
((retry_count++))
|
||||||
|
sleep $delay
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
local all_heavynodes_compatible=true
|
||||||
|
while IFS=$'\t' read -r node current_version; do
|
||||||
|
[[ -z "$node" ]] && continue
|
||||||
|
if ! es_version_can_upgrade_to_target "$current_version"; then
|
||||||
|
echo "Heavynode $node is running Elasticsearch $current_version, which is not directly upgradable to Elasticsearch $target_es_version."
|
||||||
|
all_heavynodes_compatible=false
|
||||||
|
fi
|
||||||
|
done < <(echo "$HEAVYNODE_ES_VERSIONS" | jq -r 'to_entries[] | [.key, .value] | @tsv')
|
||||||
|
|
||||||
|
while IFS= read -r heavynode_minion; do
|
||||||
|
[[ -z "$heavynode_minion" ]] && continue
|
||||||
|
if ! echo "$HEAVYNODE_ES_VERSIONS" | jq -se --arg minion "$heavynode_minion" 'add | has($minion)' > /dev/null; then
|
||||||
|
echo "Heavynode $heavynode_minion did not report an Elasticsearch version. It may be offline or still upgrading."
|
||||||
|
all_heavynodes_compatible=false
|
||||||
|
fi
|
||||||
|
done <<< "$heavynode_minions"
|
||||||
|
|
||||||
|
if [[ "$all_heavynodes_compatible" == true ]]; then
|
||||||
|
echo -e "\nAll heavynodes can upgrade to Elasticsearch $target_es_version."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "One or more heavynodes cannot upgrade directly to Elasticsearch $target_es_version. Rechecking in $delay seconds. Attempt $((retry_count + 1)) of $retries."
|
||||||
|
((retry_count++))
|
||||||
|
sleep $delay
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ ! -f "$es_verification_script" ]]; then
|
||||||
|
create_intermediate_upgrade_verification_script "$es_verification_script"
|
||||||
|
fi
|
||||||
|
|
||||||
for statefile in "${es_required_version_statefile_base}"-*; do
|
for statefile in "${es_required_version_statefile_base}"-*; do
|
||||||
[[ -f $statefile ]] || continue
|
[[ -f $statefile ]] || continue
|
||||||
|
|
||||||
@@ -1012,10 +1240,6 @@ verify_es_version_compatibility() {
|
|||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -f "$es_verification_script" ]]; then
|
|
||||||
create_intermediate_upgrade_verification_script "$es_verification_script"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "\n##############################################################################################################################\n"
|
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."
|
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."
|
||||||
if ! timeout --foreground 4000 bash "$es_verification_script" "$es_required_version_statefile_value" "$statefile"; then
|
if ! timeout --foreground 4000 bash "$es_verification_script" "$es_required_version_statefile_value" "$statefile"; then
|
||||||
@@ -1037,6 +1261,26 @@ verify_es_version_compatibility() {
|
|||||||
|
|
||||||
# shellcheck disable=SC2076 # Do not want a regex here eg usage " 8.18.8 9.0.8 " =~ " 9.0.8 "
|
# shellcheck disable=SC2076 # Do not want a regex here eg usage " 8.18.8 9.0.8 " =~ " 9.0.8 "
|
||||||
if [[ " ${es_upgrade_map[$es_version]} " =~ " $target_es_version " || "$es_version" == "$target_es_version" ]]; then
|
if [[ " ${es_upgrade_map[$es_version]} " =~ " $target_es_version " || "$es_version" == "$target_es_version" ]]; then
|
||||||
|
if ! verify_searchnodes_es_target_compatibility || ! verify_heavynodes_es_target_compatibility; then
|
||||||
|
echo -e "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
|
||||||
|
|
||||||
|
echo "One or more Searchnode(s)/Heavynode(s) cannot upgrade directly to Elasticsearch $target_es_version. This can happen with soups that include Elasticsearch upgrades being run in quick succession. Typically, this will resolve itself as the grid synchronizes. Please allow time for all Searchnodes/Heavynodes to have upgraded Elasticsearch to a compatible version with $target_es_version before running soup again to avoid potential data loss!"
|
||||||
|
|
||||||
|
if [[ -n "$HEAVYNODE_ES_VERSIONS" ]]; then
|
||||||
|
echo "Current heavynode Elasticsearch versions:"
|
||||||
|
echo "$HEAVYNODE_ES_VERSIONS" | jq '.'
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$SEARCHNODE_ES_VERSIONS" ]]; then
|
||||||
|
echo "Current searchnode Elasticsearch versions:"
|
||||||
|
echo "$SEARCHNODE_ES_VERSIONS" | jq '.nodes | to_entries | map({(.value.name): .value.version}) | sort | add'
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
|
||||||
|
|
||||||
|
exit 161
|
||||||
|
fi
|
||||||
|
|
||||||
# supported upgrade
|
# supported upgrade
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
@@ -1394,13 +1638,13 @@ main() {
|
|||||||
echo "Verifying we have the latest soup script."
|
echo "Verifying we have the latest soup script."
|
||||||
verify_latest_update_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."
|
echo "Let's see if we need to update Security Onion."
|
||||||
upgrade_check
|
upgrade_check
|
||||||
upgrade_space
|
upgrade_space
|
||||||
|
|
||||||
|
echo "Verifying Elasticsearch version compatibility across the grid before upgrading."
|
||||||
|
verify_es_version_compatibility
|
||||||
|
|
||||||
echo "Checking for Salt Master and Minion updates."
|
echo "Checking for Salt Master and Minion updates."
|
||||||
upgrade_check_salt
|
upgrade_check_salt
|
||||||
set -e
|
set -e
|
||||||
@@ -1420,7 +1664,8 @@ main() {
|
|||||||
echo "Applying $HOTFIXVERSION hotfix"
|
echo "Applying $HOTFIXVERSION hotfix"
|
||||||
# since we don't run the backup.config_backup state on import we wont snapshot previous version states and pillars
|
# since we don't run the backup.config_backup state on import we wont snapshot previous version states and pillars
|
||||||
if [[ ! "$MINION_ROLE" == "import" ]]; then
|
if [[ ! "$MINION_ROLE" == "import" ]]; then
|
||||||
backup_old_states_pillars
|
echo "Running so-config-backup script."
|
||||||
|
/sbin/so-config-backup
|
||||||
fi
|
fi
|
||||||
copy_new_files
|
copy_new_files
|
||||||
create_local_directories "/opt/so/saltstack/default"
|
create_local_directories "/opt/so/saltstack/default"
|
||||||
@@ -1476,8 +1721,8 @@ main() {
|
|||||||
# since we don't run the backup.config_backup state on import we wont snapshot previous version states and pillars
|
# since we don't run the backup.config_backup state on import we wont snapshot previous version states and pillars
|
||||||
if [[ ! "$MINION_ROLE" == "import" ]]; then
|
if [[ ! "$MINION_ROLE" == "import" ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "Creating snapshots of default and local Salt states and pillars and saving to /nsm/backup/"
|
echo "Running so-config-backup script."
|
||||||
backup_old_states_pillars
|
/sbin/so-config-backup
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-E
|
|||||||
END IF;
|
END IF;
|
||||||
END
|
END
|
||||||
\$\$;
|
\$\$;
|
||||||
|
GRANT ALL ON SCHEMA public TO "$SO_POSTGRES_USER";
|
||||||
GRANT ALL PRIVILEGES ON DATABASE "$POSTGRES_DB" TO "$SO_POSTGRES_USER";
|
GRANT ALL PRIVILEGES ON DATABASE "$POSTGRES_DB" TO "$SO_POSTGRES_USER";
|
||||||
-- Lock the SOC database down at the connect layer; PUBLIC gets CONNECT
|
-- Lock the SOC database down at the connect layer; PUBLIC gets CONNECT
|
||||||
-- by default, which would let per-minion telegraf roles open sessions
|
-- by default, which would let per-minion telegraf roles open sessions
|
||||||
|
|||||||
@@ -18,26 +18,12 @@ include:
|
|||||||
{% set TG_OUT = TELEGRAFMERGED.output | upper %}
|
{% set TG_OUT = TELEGRAFMERGED.output | upper %}
|
||||||
{% if TG_OUT in ['POSTGRES', 'BOTH'] %}
|
{% if TG_OUT in ['POSTGRES', 'BOTH'] %}
|
||||||
|
|
||||||
# docker_container.running returns as soon as the container starts, but on
|
|
||||||
# first-init docker-entrypoint.sh starts a temporary postgres with
|
|
||||||
# `listen_addresses=''` to run /docker-entrypoint-initdb.d scripts, then
|
|
||||||
# shuts it down before exec'ing the real CMD. A default pg_isready check
|
|
||||||
# (Unix socket) passes during that ephemeral phase and races the shutdown
|
|
||||||
# with "the database system is shutting down". Checking TCP readiness on
|
|
||||||
# 127.0.0.1 only succeeds after the final postgres binds the port.
|
|
||||||
postgres_wait_ready:
|
postgres_wait_ready:
|
||||||
cmd.run:
|
cmd.run:
|
||||||
- name: |
|
- name: /usr/sbin/so-postgres-wait
|
||||||
for i in $(seq 1 60); do
|
|
||||||
if docker exec so-postgres pg_isready -h 127.0.0.1 -U postgres -q 2>/dev/null; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
echo "so-postgres did not accept TCP connections within 120s" >&2
|
|
||||||
exit 1
|
|
||||||
- require:
|
- require:
|
||||||
- docker_container: so-postgres
|
- docker_container: so-postgres
|
||||||
|
- file: postgres_sbin
|
||||||
|
|
||||||
# Ensure the shared Telegraf database exists. init-db.sh only runs on a
|
# Ensure the shared Telegraf database exists. init-db.sh only runs on a
|
||||||
# fresh data dir, so hosts upgraded onto an existing /nsm/postgres volume
|
# fresh data dir, so hosts upgraded onto an existing /nsm/postgres volume
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
# Wait for the so-postgres container to accept TCP connections.
|
||||||
|
#
|
||||||
|
# docker_container.running returns as soon as the container starts, but on
|
||||||
|
# first-init docker-entrypoint.sh starts a temporary postgres with
|
||||||
|
# `listen_addresses=''` to run /docker-entrypoint-initdb.d scripts, then
|
||||||
|
# shuts it down before exec'ing the real CMD. A default pg_isready check
|
||||||
|
# (Unix socket) passes during that ephemeral phase and races the shutdown
|
||||||
|
# with "the database system is shutting down". Checking TCP readiness on
|
||||||
|
# 127.0.0.1 only succeeds after the final postgres binds the port.
|
||||||
|
#
|
||||||
|
# Usage: so-postgres-wait [iterations] [sleep_seconds]
|
||||||
|
# Default: 60 iterations, 2s sleep (~120s total).
|
||||||
|
|
||||||
|
ITERATIONS=${1:-60}
|
||||||
|
SLEEP_SECONDS=${2:-2}
|
||||||
|
|
||||||
|
for i in $(seq 1 "$ITERATIONS"); do
|
||||||
|
if docker exec so-postgres pg_isready -h 127.0.0.1 -U postgres -q 2>/dev/null; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
sleep "$SLEEP_SECONDS"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "so-postgres did not accept TCP connections within $((ITERATIONS * SLEEP_SECONDS))s" >&2
|
||||||
|
exit 1
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
include:
|
include:
|
||||||
- salt.minion
|
- salt.minion
|
||||||
|
- salt.master.boot_mine_update
|
||||||
{% if 'vrt' in salt['pillar.get']('features', []) %}
|
{% if 'vrt' in salt['pillar.get']('features', []) %}
|
||||||
- salt.cloud
|
- salt.cloud
|
||||||
- salt.cloud.reactor_config_hypervisor
|
- salt.cloud.reactor_config_hypervisor
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
|
||||||
|
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
|
||||||
|
# https://securityonion.net/license; you may not use this file except in compliance with the
|
||||||
|
# Elastic License 2.0.
|
||||||
|
|
||||||
|
# Manages /etc/systemd/system/so-boot-mine-update.service, a manager-only
|
||||||
|
# Type=oneshot unit that pushes `salt '*' mine.update` once per boot, ordered
|
||||||
|
# before so-boot-highstate.service so mine-backed pillars (node IPs, ES/Redis/
|
||||||
|
# Logstash discovery) are fresh before the boot highstate renders them.
|
||||||
|
|
||||||
|
include:
|
||||||
|
- systemd.reload
|
||||||
|
|
||||||
|
so_boot_mine_update_unit_file:
|
||||||
|
file.managed:
|
||||||
|
- name: /etc/systemd/system/so-boot-mine-update.service
|
||||||
|
- source: salt://salt/service/so-boot-mine-update.service
|
||||||
|
- onchanges_in:
|
||||||
|
- module: systemd_reload
|
||||||
|
|
||||||
|
# Only enable once setup is complete. Until then the gate file is missing and
|
||||||
|
# the unit's own ConditionPathExists would no-op it anyway.
|
||||||
|
so_boot_mine_update_service:
|
||||||
|
service.enabled:
|
||||||
|
- name: so-boot-mine-update.service
|
||||||
|
- onlyif: test -e /opt/so/state/setup-complete
|
||||||
|
- require:
|
||||||
|
- file: so_boot_mine_update_unit_file
|
||||||
|
- module: systemd_reload
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
|
||||||
|
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
|
||||||
|
# https://securityonion.net/license; you may not use this file except in compliance with the
|
||||||
|
# Elastic License 2.0.
|
||||||
|
|
||||||
|
# Manages /etc/systemd/system/so-boot-highstate.service, a Type=oneshot
|
||||||
|
# RemainAfterExit=yes unit that runs `salt-call state.highstate` exactly once
|
||||||
|
# per system boot. Replaces the legacy `startup_states: highstate` minion
|
||||||
|
# config, which fired on every salt-minion service restart (causing a redundant
|
||||||
|
# highstate whenever a highstate itself restarted salt-minion).
|
||||||
|
|
||||||
|
include:
|
||||||
|
- systemd.reload
|
||||||
|
|
||||||
|
so_boot_highstate_unit_file:
|
||||||
|
file.managed:
|
||||||
|
- name: /etc/systemd/system/so-boot-highstate.service
|
||||||
|
- source: salt://salt/service/so-boot-highstate.service
|
||||||
|
- onchanges_in:
|
||||||
|
- module: systemd_reload
|
||||||
|
|
||||||
|
# Only enable once setup is complete. Until then the gate file is missing and
|
||||||
|
# the unit's own ConditionPathExists would no-op it anyway -- this just keeps
|
||||||
|
# `systemctl is-enabled` honest for the sync_es_users gate.
|
||||||
|
so_boot_highstate_service:
|
||||||
|
service.enabled:
|
||||||
|
- name: so-boot-highstate.service
|
||||||
|
- onlyif: test -e /opt/so/state/setup-complete
|
||||||
|
- require:
|
||||||
|
- file: so_boot_highstate_unit_file
|
||||||
|
- module: systemd_reload
|
||||||
@@ -17,6 +17,7 @@ include:
|
|||||||
- repo.client
|
- repo.client
|
||||||
- salt.mine_functions
|
- salt.mine_functions
|
||||||
- salt.minion.service_file
|
- salt.minion.service_file
|
||||||
|
- salt.minion.boot_highstate
|
||||||
{% if GLOBALS.is_manager %}
|
{% if GLOBALS.is_manager %}
|
||||||
- ca.signing_policy
|
- ca.signing_policy
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -80,11 +81,33 @@ set_log_levels:
|
|||||||
- "log_level: info"
|
- "log_level: info"
|
||||||
- "log_level_logfile: info"
|
- "log_level_logfile: info"
|
||||||
|
|
||||||
enable_startup_states:
|
# startup_states: highstate caused a full highstate to run on every
|
||||||
file.uncomment:
|
# salt-minion service start, including the restart triggered when a highstate
|
||||||
|
# itself modified the minion config (beacons, mine, unit file). Replaced by
|
||||||
|
# so-boot-highstate.service (managed in salt.minion.boot_highstate), which
|
||||||
|
# runs once per system boot only. Strip the line from /etc/salt/minion on
|
||||||
|
# upgrade; both the commented and uncommented forms historically existed.
|
||||||
|
remove_startup_states:
|
||||||
|
file.line:
|
||||||
- name: /etc/salt/minion
|
- name: /etc/salt/minion
|
||||||
- regex: '^startup_states: highstate$'
|
- match: 'startup_states: highstate'
|
||||||
- unless: pgrep so-setup
|
- mode: delete
|
||||||
|
|
||||||
|
# Upgrade-path bridge: systems that already passed setup under the old gate
|
||||||
|
# (`grep -x 'startup_states: highstate' /etc/salt/minion`) get a /opt/so/state/setup-complete
|
||||||
|
# marker so so-boot-highstate.service can be enabled and the so-user_sync cron
|
||||||
|
# in sync_es_users.sls keeps installing. Setup-in-progress systems instead get
|
||||||
|
# the marker from `mark_setup_complete` in setup/so-functions at the right
|
||||||
|
# moment. `replace: false` means we never overwrite a marker once written.
|
||||||
|
mark_setup_complete_for_upgrades:
|
||||||
|
file.managed:
|
||||||
|
- name: /opt/so/state/setup-complete
|
||||||
|
- replace: false
|
||||||
|
- makedirs: True
|
||||||
|
- onlyif: "grep -qx 'startup_states: highstate' /etc/salt/minion"
|
||||||
|
- require_in:
|
||||||
|
- file: remove_startup_states
|
||||||
|
- service: so_boot_highstate_service
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Security Onion boot-time highstate (runs once per boot)
|
||||||
|
After=salt-minion.service network-online.target docker.service
|
||||||
|
Wants=network-online.target docker.service
|
||||||
|
Requires=salt-minion.service
|
||||||
|
ConditionPathExists=/opt/so/state/setup-complete
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
RemainAfterExit=yes
|
||||||
|
ExecStart=/usr/bin/salt-call state.highstate -l info queue=True
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Security Onion boot-time grid mine.update (managers, runs once per boot before highstate)
|
||||||
|
After=salt-master.service salt-minion.service network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
Requires=salt-master.service salt-minion.service
|
||||||
|
Before=so-boot-highstate.service
|
||||||
|
ConditionPathExists=/opt/so/state/setup-complete
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
RemainAfterExit=yes
|
||||||
|
ExecStart=/usr/sbin/so-boot-mine-update
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -8,11 +8,6 @@ set_role_grain:
|
|||||||
- name: role
|
- name: role
|
||||||
- value: so-{{ grains.id.split("_") | last }}
|
- value: so-{{ grains.id.split("_") | last }}
|
||||||
|
|
||||||
set_highstate:
|
|
||||||
file.append:
|
|
||||||
- name: /etc/salt/minion
|
|
||||||
- text: 'startup_states: highstate'
|
|
||||||
|
|
||||||
enable_salt_minion:
|
enable_salt_minion:
|
||||||
service.enabled:
|
service.enabled:
|
||||||
- name: salt-minion
|
- name: salt-minion
|
||||||
|
|||||||
@@ -1519,6 +1519,16 @@ soc:
|
|||||||
serviceAccountJSON: ""
|
serviceAccountJSON: ""
|
||||||
serviceAccountLocation: ""
|
serviceAccountLocation: ""
|
||||||
healthTimeoutSeconds: 5
|
healthTimeoutSeconds: 5
|
||||||
|
onionconfig:
|
||||||
|
saltstackDir: /opt/so/saltstack
|
||||||
|
bypassEnabled: false
|
||||||
|
postgres:
|
||||||
|
host: ""
|
||||||
|
port: 5432
|
||||||
|
sslMode: "allow"
|
||||||
|
database: securityonion
|
||||||
|
user: ""
|
||||||
|
password: ""
|
||||||
salt:
|
salt:
|
||||||
queueDir: /opt/sensoroni/queue
|
queueDir: /opt/sensoroni/queue
|
||||||
timeoutMs: 45000
|
timeoutMs: 45000
|
||||||
|
|||||||
@@ -16,6 +16,14 @@
|
|||||||
{% do SOCMERGED.config.server.update({'additionalCA': MANAGERMERGED.additionalCA}) %}
|
{% do SOCMERGED.config.server.update({'additionalCA': MANAGERMERGED.additionalCA}) %}
|
||||||
{% do SOCMERGED.config.server.update({'insecureSkipVerify': MANAGERMERGED.insecureSkipVerify}) %}
|
{% do SOCMERGED.config.server.update({'insecureSkipVerify': MANAGERMERGED.insecureSkipVerify}) %}
|
||||||
|
|
||||||
|
{% if not SOCMERGED.config.server.modules.postgres.host %}
|
||||||
|
{% do SOCMERGED.config.server.modules.postgres.update({'host': GLOBALS.manager}) %}
|
||||||
|
{% endif %}
|
||||||
|
{% if not SOCMERGED.config.server.modules.postgres.password %}
|
||||||
|
{% do SOCMERGED.config.server.modules.postgres.update({'password': salt['pillar.get']('postgres:auth:users:so_postgres_user:pass', '')}) %}
|
||||||
|
{% do SOCMERGED.config.server.modules.postgres.update({'user': salt['pillar.get']('postgres:auth:users:so_postgres_user:user', 'so_postgres')}) %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{# if SOCMERGED.config.server.modules.cases == httpcase details come from the soc pillar #}
|
{# if SOCMERGED.config.server.modules.cases == httpcase details come from the soc pillar #}
|
||||||
{% if SOCMERGED.config.server.modules.cases != 'soc' %}
|
{% if SOCMERGED.config.server.modules.cases != 'soc' %}
|
||||||
{% do SOCMERGED.config.server.modules.elastic.update({'casesEnabled': false}) %}
|
{% do SOCMERGED.config.server.modules.elastic.update({'casesEnabled': false}) %}
|
||||||
|
|||||||
@@ -453,6 +453,42 @@ soc:
|
|||||||
description: Duration (in milliseconds) that must elapse after a grid node fails to check-in before the node will be marked offline (fault).
|
description: Duration (in milliseconds) that must elapse after a grid node fails to check-in before the node will be marked offline (fault).
|
||||||
global: True
|
global: True
|
||||||
advanced: True
|
advanced: True
|
||||||
|
onionconfig:
|
||||||
|
saltstackDir:
|
||||||
|
description: Root directory containing the SaltStack tree that SOC reads and writes configuration from. Should not be changed under normal circumstances.
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
bypassEnabled:
|
||||||
|
description: When enabled, errors encountered while reading the SaltStack pillar tree (missing files, unreadable directories, etc.) are logged but do not prevent SOC from starting or serving settings. Intended for advanced troubleshooting and recovery scenarios when the pillar tree is partially unreadable.
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
forcedType: bool
|
||||||
|
postgres:
|
||||||
|
host:
|
||||||
|
description: Hostname or IP address of the PostgreSQL server used by SOC. Defaults to the manager hostname.
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
port:
|
||||||
|
description: Port of the PostgreSQL server used by SOC.
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
sslMode:
|
||||||
|
description: "Use encrypted connections to the PostgreSQL server. Must be one of the following values: disable, allow, prefer, require, verify-ca, verify-full. Defaults to allow."
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
database:
|
||||||
|
description: Database used by SOC to authenticate to the PostgreSQL server.
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
user:
|
||||||
|
description: Username used by SOC to authenticate to the PostgreSQL server.
|
||||||
|
global: True
|
||||||
|
advanced: True
|
||||||
|
password:
|
||||||
|
description: Password used by SOC to authenticate to the PostgreSQL server.
|
||||||
|
global: True
|
||||||
|
sensitive: True
|
||||||
|
advanced: True
|
||||||
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.
|
||||||
@@ -818,6 +854,7 @@ soc:
|
|||||||
description: List of available external tools visible in the SOC UI. Each tool is defined in JSON object notation, and must include the "name" key and "link" key, where the link is the tool's URL.
|
description: List of available external tools visible in the SOC UI. Each tool is defined in JSON object notation, and must include the "name" key and "link" key, where the link is the tool's URL.
|
||||||
global: True
|
global: True
|
||||||
advanced: True
|
advanced: True
|
||||||
|
multiline: True
|
||||||
forcedType: "[]{}"
|
forcedType: "[]{}"
|
||||||
exportNodeId:
|
exportNodeId:
|
||||||
description: The node ID on which export jobs will be executed.
|
description: The node ID on which export jobs will be executed.
|
||||||
|
|||||||
+11
-6
@@ -539,16 +539,19 @@ configure_minion() {
|
|||||||
" x509_v2: true"\
|
" x509_v2: true"\
|
||||||
"log_level: info"\
|
"log_level: info"\
|
||||||
"log_level_logfile: info"\
|
"log_level_logfile: info"\
|
||||||
"log_file: /opt/so/log/salt/minion"\
|
"log_file: /opt/so/log/salt/minion" >> "$minion_config"
|
||||||
"#startup_states: highstate" >> "$minion_config"
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
checkin_at_boot() {
|
mark_setup_complete() {
|
||||||
local minion_config=/etc/salt/minion
|
# Writes the setup-complete marker. Salt's so-boot-highstate.service
|
||||||
|
# (boot-time oneshot) and the so-user_sync cron gate in
|
||||||
|
# salt/manager/sync_es_users.sls both key off this file.
|
||||||
|
local marker=/opt/so/state/setup-complete
|
||||||
|
|
||||||
info "Enabling checkin at boot"
|
info "Marking setup as complete"
|
||||||
sed -i 's/#startup_states: highstate/startup_states: highstate/' "$minion_config"
|
mkdir -p "$(dirname "$marker")"
|
||||||
|
touch "$marker"
|
||||||
}
|
}
|
||||||
|
|
||||||
check_requirements() {
|
check_requirements() {
|
||||||
@@ -977,6 +980,8 @@ docker_seed_registry() {
|
|||||||
docker_seed_update_percent=25
|
docker_seed_update_percent=25
|
||||||
|
|
||||||
update_docker_containers 'netinstall' '' 'docker_seed_update' '/dev/stdout' 2>&1 | tee -a "$setup_log"
|
update_docker_containers 'netinstall' '' 'docker_seed_update' '/dev/stdout' 2>&1 | tee -a "$setup_log"
|
||||||
|
# Use pipe exit status of 'update_docker_containers' for return code
|
||||||
|
return ${PIPESTATUS[0]}
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-2
@@ -223,6 +223,8 @@ if [ -n "$test_profile" ]; then
|
|||||||
WEBPASSWD1=0n10nus3r
|
WEBPASSWD1=0n10nus3r
|
||||||
WEBPASSWD2=0n10nus3r
|
WEBPASSWD2=0n10nus3r
|
||||||
NODE_DESCRIPTION="${HOSTNAME} - ${install_type} - ${MSRVIP_OFFSET}"
|
NODE_DESCRIPTION="${HOSTNAME} - ${install_type} - ${MSRVIP_OFFSET}"
|
||||||
|
# opt out of telemetry for automated testing
|
||||||
|
telemetry=1
|
||||||
|
|
||||||
update_sudoers_for_testing
|
update_sudoers_for_testing
|
||||||
fi
|
fi
|
||||||
@@ -767,7 +769,10 @@ if ! [[ -f $install_opt_file ]]; then
|
|||||||
title "Applying the registry state"
|
title "Applying the registry state"
|
||||||
logCmd "salt-call state.apply -l info registry"
|
logCmd "salt-call state.apply -l info registry"
|
||||||
title "Seeding the docker registry"
|
title "Seeding the docker registry"
|
||||||
docker_seed_registry
|
if ! docker_seed_registry; then
|
||||||
|
error "Failed to seed the docker registry"
|
||||||
|
fail_setup
|
||||||
|
fi
|
||||||
title "Applying the manager state"
|
title "Applying the manager state"
|
||||||
logCmd "salt-call state.apply -l info manager"
|
logCmd "salt-call state.apply -l info manager"
|
||||||
logCmd "salt-call state.apply influxdb -l info"
|
logCmd "salt-call state.apply influxdb -l info"
|
||||||
@@ -792,7 +797,7 @@ if ! [[ -f $install_opt_file ]]; then
|
|||||||
error "Failed to run so-elastic-fleet-setup"
|
error "Failed to run so-elastic-fleet-setup"
|
||||||
fail_setup
|
fail_setup
|
||||||
fi
|
fi
|
||||||
checkin_at_boot
|
mark_setup_complete
|
||||||
set_initial_firewall_access
|
set_initial_firewall_access
|
||||||
initialize_elasticsearch_indices "so-case so-casehistory so-assistant-session so-assistant-chat"
|
initialize_elasticsearch_indices "so-case so-casehistory so-assistant-session so-assistant-chat"
|
||||||
# run a final highstate before enabling scheduled highstates.
|
# run a final highstate before enabling scheduled highstates.
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user