mirror of
https://github.com/Security-Onion-Solutions/securityonion.git
synced 2026-06-12 13:19:22 +02:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| acc9b8062e | |||
| c6c538363d |
@@ -11,7 +11,6 @@ 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.1.0-20260528 ISO image released on 2026/05/28
|
### 3.0.0-20260331 ISO image released on 2026/03/31
|
||||||
|
|
||||||
|
|
||||||
### Download and Verify
|
### Download and Verify
|
||||||
|
|
||||||
3.1.0-20260528 ISO image:
|
3.0.0-20260331 ISO image:
|
||||||
https://download.securityonion.net/file/securityonion/securityonion-3.1.0-20260528.iso
|
https://download.securityonion.net/file/securityonion/securityonion-3.0.0-20260331.iso
|
||||||
|
|
||||||
MD5: 9D6FF58DEEE24089D722C73169765B3E
|
MD5: ECD318A1662A6FDE0EF213F5A9BD4B07
|
||||||
SHA1: 2B8B816B6CEC3B7F96B3C5E040EBF502DD2C412F
|
SHA1: E55BE314440CCF3392DC0B06BC5E270B43176D9C
|
||||||
SHA256: 62FAB57E247C843D6A04F0796D8162C732B65D82FC3E4A59D087135B9FD32912
|
SHA256: 7FC47405E335CBE5C2B6C51FE7AC60248F35CBE504907B8B5A33822B23F8F4D5
|
||||||
|
|
||||||
Signature for ISO image:
|
Signature for ISO image:
|
||||||
https://github.com/Security-Onion-Solutions/securityonion/raw/3/main/sigs/securityonion-3.1.0-20260528.iso.sig
|
https://github.com/Security-Onion-Solutions/securityonion/raw/3/main/sigs/securityonion-3.0.0-20260331.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.1.0-20260528.iso.sig
|
wget https://github.com/Security-Onion-Solutions/securityonion/raw/3/main/sigs/securityonion-3.0.0-20260331.iso.sig
|
||||||
```
|
```
|
||||||
|
|
||||||
Download the ISO image:
|
Download the ISO image:
|
||||||
```
|
```
|
||||||
wget https://download.securityonion.net/file/securityonion/securityonion-3.1.0-20260528.iso
|
wget https://download.securityonion.net/file/securityonion/securityonion-3.0.0-20260331.iso
|
||||||
```
|
```
|
||||||
|
|
||||||
Verify the downloaded ISO image using the signature file:
|
Verify the downloaded ISO image using the signature file:
|
||||||
```
|
```
|
||||||
gpg --verify securityonion-3.1.0-20260528.iso.sig securityonion-3.1.0-20260528.iso
|
gpg --verify securityonion-3.0.0-20260331.iso.sig securityonion-3.0.0-20260331.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 Wed 27 May 2026 03:03:59 PM EDT using RSA key ID FE507013
|
gpg: Signature made Mon 30 Mar 2026 06:22:14 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.
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
elasticsearch:
|
||||||
|
index_settings:
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
|
|
||||||
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
|
|
||||||
# https://securityonion.net/license; you may not use this file except in compliance with the
|
|
||||||
# Elastic License 2.0.
|
|
||||||
|
|
||||||
# Per-minion Telegraf Postgres credentials. so-telegraf-cred on the manager is
|
|
||||||
# the single writer; it mutates /opt/so/saltstack/local/pillar/telegraf/creds.sls
|
|
||||||
# under flock. Pillar_roots order (local before default) means the populated
|
|
||||||
# copy shadows this default on any real grid; this file exists so the pillar
|
|
||||||
# key is always defined on fresh installs and when no minions have creds yet.
|
|
||||||
telegraf:
|
|
||||||
postgres_creds: {}
|
|
||||||
+3
-21
@@ -17,7 +17,6 @@ base:
|
|||||||
- sensoroni.adv_sensoroni
|
- sensoroni.adv_sensoroni
|
||||||
- telegraf.soc_telegraf
|
- telegraf.soc_telegraf
|
||||||
- telegraf.adv_telegraf
|
- telegraf.adv_telegraf
|
||||||
- telegraf.creds
|
|
||||||
- versionlock.soc_versionlock
|
- versionlock.soc_versionlock
|
||||||
- versionlock.adv_versionlock
|
- versionlock.adv_versionlock
|
||||||
- soc.license
|
- soc.license
|
||||||
@@ -39,9 +38,6 @@ base:
|
|||||||
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/elasticsearch/auth.sls') %}
|
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/elasticsearch/auth.sls') %}
|
||||||
- elasticsearch.auth
|
- elasticsearch.auth
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/postgres/auth.sls') %}
|
|
||||||
- postgres.auth
|
|
||||||
{% endif %}
|
|
||||||
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/kibana/secrets.sls') %}
|
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/kibana/secrets.sls') %}
|
||||||
- kibana.secrets
|
- kibana.secrets
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -64,8 +60,6 @@ base:
|
|||||||
- redis.adv_redis
|
- redis.adv_redis
|
||||||
- influxdb.soc_influxdb
|
- influxdb.soc_influxdb
|
||||||
- influxdb.adv_influxdb
|
- influxdb.adv_influxdb
|
||||||
- postgres.soc_postgres
|
|
||||||
- postgres.adv_postgres
|
|
||||||
- elasticsearch.nodes
|
- elasticsearch.nodes
|
||||||
- elasticsearch.soc_elasticsearch
|
- elasticsearch.soc_elasticsearch
|
||||||
- elasticsearch.adv_elasticsearch
|
- elasticsearch.adv_elasticsearch
|
||||||
@@ -103,12 +97,10 @@ base:
|
|||||||
- node_data.ips
|
- node_data.ips
|
||||||
- secrets
|
- secrets
|
||||||
- healthcheck.eval
|
- healthcheck.eval
|
||||||
|
- elasticsearch.index_templates
|
||||||
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/elasticsearch/auth.sls') %}
|
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/elasticsearch/auth.sls') %}
|
||||||
- elasticsearch.auth
|
- elasticsearch.auth
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/postgres/auth.sls') %}
|
|
||||||
- postgres.auth
|
|
||||||
{% endif %}
|
|
||||||
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/kibana/secrets.sls') %}
|
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/kibana/secrets.sls') %}
|
||||||
- kibana.secrets
|
- kibana.secrets
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -134,8 +126,6 @@ base:
|
|||||||
- redis.adv_redis
|
- redis.adv_redis
|
||||||
- influxdb.soc_influxdb
|
- influxdb.soc_influxdb
|
||||||
- influxdb.adv_influxdb
|
- influxdb.adv_influxdb
|
||||||
- postgres.soc_postgres
|
|
||||||
- postgres.adv_postgres
|
|
||||||
- backup.soc_backup
|
- backup.soc_backup
|
||||||
- backup.adv_backup
|
- backup.adv_backup
|
||||||
- zeek.soc_zeek
|
- zeek.soc_zeek
|
||||||
@@ -152,12 +142,10 @@ base:
|
|||||||
- logstash.nodes
|
- logstash.nodes
|
||||||
- logstash.soc_logstash
|
- logstash.soc_logstash
|
||||||
- logstash.adv_logstash
|
- logstash.adv_logstash
|
||||||
|
- elasticsearch.index_templates
|
||||||
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/elasticsearch/auth.sls') %}
|
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/elasticsearch/auth.sls') %}
|
||||||
- elasticsearch.auth
|
- elasticsearch.auth
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/postgres/auth.sls') %}
|
|
||||||
- postgres.auth
|
|
||||||
{% endif %}
|
|
||||||
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/kibana/secrets.sls') %}
|
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/kibana/secrets.sls') %}
|
||||||
- kibana.secrets
|
- kibana.secrets
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -172,8 +160,6 @@ base:
|
|||||||
- redis.adv_redis
|
- redis.adv_redis
|
||||||
- influxdb.soc_influxdb
|
- influxdb.soc_influxdb
|
||||||
- influxdb.adv_influxdb
|
- influxdb.adv_influxdb
|
||||||
- postgres.soc_postgres
|
|
||||||
- postgres.adv_postgres
|
|
||||||
- elasticsearch.nodes
|
- elasticsearch.nodes
|
||||||
- elasticsearch.soc_elasticsearch
|
- elasticsearch.soc_elasticsearch
|
||||||
- elasticsearch.adv_elasticsearch
|
- elasticsearch.adv_elasticsearch
|
||||||
@@ -270,12 +256,10 @@ base:
|
|||||||
'*_import':
|
'*_import':
|
||||||
- node_data.ips
|
- node_data.ips
|
||||||
- secrets
|
- secrets
|
||||||
|
- elasticsearch.index_templates
|
||||||
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/elasticsearch/auth.sls') %}
|
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/elasticsearch/auth.sls') %}
|
||||||
- elasticsearch.auth
|
- elasticsearch.auth
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/postgres/auth.sls') %}
|
|
||||||
- postgres.auth
|
|
||||||
{% endif %}
|
|
||||||
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/kibana/secrets.sls') %}
|
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/kibana/secrets.sls') %}
|
||||||
- kibana.secrets
|
- kibana.secrets
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -301,8 +285,6 @@ base:
|
|||||||
- redis.adv_redis
|
- redis.adv_redis
|
||||||
- influxdb.soc_influxdb
|
- influxdb.soc_influxdb
|
||||||
- influxdb.adv_influxdb
|
- influxdb.adv_influxdb
|
||||||
- postgres.soc_postgres
|
|
||||||
- postgres.adv_postgres
|
|
||||||
- zeek.soc_zeek
|
- zeek.soc_zeek
|
||||||
- zeek.adv_zeek
|
- zeek.adv_zeek
|
||||||
- bpf.soc_bpf
|
- bpf.soc_bpf
|
||||||
|
|||||||
@@ -29,14 +29,10 @@
|
|||||||
'manager',
|
'manager',
|
||||||
'nginx',
|
'nginx',
|
||||||
'influxdb',
|
'influxdb',
|
||||||
'postgres',
|
|
||||||
'postgres.auth',
|
|
||||||
'soc',
|
'soc',
|
||||||
'kratos',
|
'kratos',
|
||||||
'hydra',
|
'hydra',
|
||||||
'elasticfleet',
|
'elasticfleet',
|
||||||
'elasticfleet.manager',
|
|
||||||
'elasticsearch.cluster',
|
|
||||||
'elastic-fleet-package-registry',
|
'elastic-fleet-package-registry',
|
||||||
'utility'
|
'utility'
|
||||||
] %}
|
] %}
|
||||||
@@ -45,8 +41,7 @@
|
|||||||
'suricata',
|
'suricata',
|
||||||
'healthcheck',
|
'healthcheck',
|
||||||
'tcpreplay',
|
'tcpreplay',
|
||||||
'zeek',
|
'zeek'
|
||||||
'strelka'
|
|
||||||
] %}
|
] %}
|
||||||
|
|
||||||
{% set kafka_states = [
|
{% set kafka_states = [
|
||||||
@@ -81,33 +76,33 @@
|
|||||||
),
|
),
|
||||||
'so-heavynode': (
|
'so-heavynode': (
|
||||||
sensor_states +
|
sensor_states +
|
||||||
['elasticagent', 'elasticsearch', 'elasticsearch.cluster', 'logstash', 'redis', 'nginx']
|
['elasticagent', 'elasticsearch', 'logstash', 'redis', 'nginx']
|
||||||
),
|
),
|
||||||
'so-idh': (
|
'so-idh': (
|
||||||
['idh']
|
['idh']
|
||||||
),
|
),
|
||||||
'so-import': (
|
'so-import': (
|
||||||
manager_states +
|
manager_states +
|
||||||
sensor_states | reject('equalto', 'strelka') | reject('equalto', 'healthcheck') | list +
|
sensor_states | reject('equalto', 'healthcheck') | list +
|
||||||
['elasticsearch', 'elasticsearch.auth', 'kibana', 'kibana.secrets', 'logstash.ssl', 'strelka.manager']
|
['elasticsearch', 'elasticsearch.auth', 'kibana', 'kibana.secrets', 'logstash.ssl']
|
||||||
),
|
),
|
||||||
'so-manager': (
|
'so-manager': (
|
||||||
manager_states +
|
manager_states +
|
||||||
['salt.cloud', 'libvirt.packages', 'libvirt.ssh.users', 'strelka.manager'] +
|
['salt.cloud', 'libvirt.packages', 'libvirt.ssh.users'] +
|
||||||
stig_states +
|
stig_states +
|
||||||
kafka_states +
|
kafka_states +
|
||||||
elastic_stack_states
|
elastic_stack_states
|
||||||
),
|
),
|
||||||
'so-managerhype': (
|
'so-managerhype': (
|
||||||
manager_states +
|
manager_states +
|
||||||
['salt.cloud', 'strelka.manager', 'hypervisor', 'libvirt'] +
|
['salt.cloud', 'hypervisor', 'libvirt'] +
|
||||||
stig_states +
|
stig_states +
|
||||||
kafka_states +
|
kafka_states +
|
||||||
elastic_stack_states
|
elastic_stack_states
|
||||||
),
|
),
|
||||||
'so-managersearch': (
|
'so-managersearch': (
|
||||||
manager_states +
|
manager_states +
|
||||||
['salt.cloud', 'libvirt.packages', 'libvirt.ssh.users', 'strelka.manager'] +
|
['salt.cloud', 'libvirt.packages', 'libvirt.ssh.users'] +
|
||||||
stig_states +
|
stig_states +
|
||||||
kafka_states +
|
kafka_states +
|
||||||
elastic_stack_states
|
elastic_stack_states
|
||||||
|
|||||||
@@ -32,4 +32,3 @@ so_config_backup:
|
|||||||
- daymonth: '*'
|
- daymonth: '*'
|
||||||
- month: '*'
|
- month: '*'
|
||||||
- dayweek: '*'
|
- dayweek: '*'
|
||||||
|
|
||||||
|
|||||||
@@ -25,11 +25,9 @@ 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 if they exist
|
# Loop through all paths defined in global.sls, and append them to backup file
|
||||||
{%- 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
|
||||||
|
|||||||
@@ -54,20 +54,6 @@ x509_signing_policies:
|
|||||||
- extendedKeyUsage: serverAuth
|
- extendedKeyUsage: serverAuth
|
||||||
- days_valid: 820
|
- days_valid: 820
|
||||||
- copypath: /etc/pki/issued_certs/
|
- copypath: /etc/pki/issued_certs/
|
||||||
postgres:
|
|
||||||
- minions: '*'
|
|
||||||
- signing_private_key: /etc/pki/ca.key
|
|
||||||
- signing_cert: /etc/pki/ca.crt
|
|
||||||
- C: US
|
|
||||||
- ST: Utah
|
|
||||||
- L: Salt Lake City
|
|
||||||
- basicConstraints: "critical CA:false"
|
|
||||||
- keyUsage: "critical keyEncipherment"
|
|
||||||
- subjectKeyIdentifier: hash
|
|
||||||
- authorityKeyIdentifier: keyid,issuer:always
|
|
||||||
- extendedKeyUsage: serverAuth
|
|
||||||
- days_valid: 820
|
|
||||||
- copypath: /etc/pki/issued_certs/
|
|
||||||
elasticfleet:
|
elasticfleet:
|
||||||
- minions: '*'
|
- minions: '*'
|
||||||
- signing_private_key: /etc/pki/ca.key
|
- signing_private_key: /etc/pki/ca.key
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ container_list() {
|
|||||||
"so-hydra"
|
"so-hydra"
|
||||||
"so-nginx"
|
"so-nginx"
|
||||||
"so-pcaptools"
|
"so-pcaptools"
|
||||||
"so-postgres"
|
|
||||||
"so-soc"
|
"so-soc"
|
||||||
"so-suricata"
|
"so-suricata"
|
||||||
"so-telegraf"
|
"so-telegraf"
|
||||||
@@ -56,11 +55,8 @@ container_list() {
|
|||||||
"so-logstash"
|
"so-logstash"
|
||||||
"so-nginx"
|
"so-nginx"
|
||||||
"so-pcaptools"
|
"so-pcaptools"
|
||||||
"so-postgres"
|
|
||||||
"so-redis"
|
"so-redis"
|
||||||
"so-soc"
|
"so-soc"
|
||||||
"so-strelka-backend"
|
|
||||||
"so-strelka-manager"
|
|
||||||
"so-suricata"
|
"so-suricata"
|
||||||
"so-telegraf"
|
"so-telegraf"
|
||||||
"so-zeek"
|
"so-zeek"
|
||||||
@@ -164,8 +160,8 @@ update_docker_containers() {
|
|||||||
# Pull down the trusted docker image
|
# Pull down the trusted docker image
|
||||||
run_check_net_err \
|
run_check_net_err \
|
||||||
"docker pull $CONTAINER_REGISTRY/$IMAGEREPO/$image" \
|
"docker pull $CONTAINER_REGISTRY/$IMAGEREPO/$image" \
|
||||||
"Could not pull $image, please ensure connectivity to $CONTAINER_REGISTRY" >> "$LOG_FILE" 2>&1
|
"Could not pull $image, please ensure connectivity to $CONTAINER_REGISTRY" >> "$LOG_FILE" 2>&1
|
||||||
|
|
||||||
# Get signature
|
# Get signature
|
||||||
run_check_net_err \
|
run_check_net_err \
|
||||||
"curl --retry 5 --retry-delay 60 -A '$CURLTYPE/$CURRENTVERSION/$OS/$(uname -r)' $sig_url --output $SIGNPATH/$image.sig" \
|
"curl --retry 5 --retry-delay 60 -A '$CURLTYPE/$CURRENTVERSION/$OS/$(uname -r)' $sig_url --output $SIGNPATH/$image.sig" \
|
||||||
@@ -188,27 +184,8 @@ update_docker_containers() {
|
|||||||
if [ -z "$HOSTNAME" ]; then
|
if [ -z "$HOSTNAME" ]; then
|
||||||
HOSTNAME=$(hostname)
|
HOSTNAME=$(hostname)
|
||||||
fi
|
fi
|
||||||
docker tag $CONTAINER_REGISTRY/$IMAGEREPO/$image $HOSTNAME:5000/$IMAGEREPO/$image >> "$LOG_FILE" 2>&1 || {
|
docker tag $CONTAINER_REGISTRY/$IMAGEREPO/$image $HOSTNAME:5000/$IMAGEREPO/$image >> "$LOG_FILE" 2>&1
|
||||||
echo "Unable to tag $image" >> "$LOG_FILE" 2>&1
|
docker push $HOSTNAME:5000/$IMAGEREPO/$image >> "$LOG_FILE" 2>&1
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
# Push to the embedded registry via a registry-to-registry copy. Avoids
|
|
||||||
# `docker push`, which on Docker 29.x with the containerd image store
|
|
||||||
# represents freshly-pulled images as an index whose layer content
|
|
||||||
# isn't reachable through the push path. The local `docker tag` above
|
|
||||||
# is preserved so so-image-pull's `:5000` existence check still works.
|
|
||||||
# Pin to the digest already gpg-verified above so we copy exactly the
|
|
||||||
# bytes we approved.
|
|
||||||
local VERIFIED_REF
|
|
||||||
VERIFIED_REF=$(echo "$DOCKERINSPECT" | jq -r ".[0].RepoDigests[] | select(. | contains(\"$CONTAINER_REGISTRY\"))" | head -n 1)
|
|
||||||
if [ -z "$VERIFIED_REF" ] || [ "$VERIFIED_REF" = "null" ]; then
|
|
||||||
echo "Unable to determine verified digest for $image" >> "$LOG_FILE" 2>&1
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
docker buildx imagetools create --tag $HOSTNAME:5000/$IMAGEREPO/$image "$VERIFIED_REF" >> "$LOG_FILE" 2>&1 || {
|
|
||||||
echo "Unable to copy $image to embedded registry" >> "$LOG_FILE" 2>&1
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "There is a problem downloading the $image image. Details: " >> "$LOG_FILE" 2>&1
|
echo "There is a problem downloading the $image image. Details: " >> "$LOG_FILE" 2>&1
|
||||||
|
|||||||
@@ -165,8 +165,6 @@ if [[ $EXCLUDE_FALSE_POSITIVE_ERRORS == 'Y' ]]; then
|
|||||||
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|upgrading component template" # false positive (elasticsearch index or template names contain 'error')
|
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|upgrading component template" # false positive (elasticsearch index or template names contain 'error')
|
||||||
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|upgrading composable template" # false positive (elasticsearch composable template names contain 'error')
|
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|upgrading composable template" # false positive (elasticsearch composable template names contain 'error')
|
||||||
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Error while parsing document for index \[.ds-logs-kratos-so-.*object mapping for \[file\]" # false positive (mapping error occuring BEFORE kratos index has rolled over in 2.4.210)
|
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Error while parsing document for index \[.ds-logs-kratos-so-.*object mapping for \[file\]" # false positive (mapping error occuring BEFORE kratos index has rolled over in 2.4.210)
|
||||||
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|No such container" # false positive (telegraf trying to run stats on an old container)
|
|
||||||
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|passwords do not match" # false positive (automated hydra test)
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $EXCLUDE_KNOWN_ERRORS == 'Y' ]]; then
|
if [[ $EXCLUDE_KNOWN_ERRORS == 'Y' ]]; then
|
||||||
@@ -229,7 +227,7 @@ if [[ $EXCLUDE_KNOWN_ERRORS == 'Y' ]]; then
|
|||||||
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|from NIC checksum offloading" # zeek reporter.log
|
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|from NIC checksum offloading" # zeek reporter.log
|
||||||
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|marked for removal" # docker container getting recycled
|
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|marked for removal" # docker container getting recycled
|
||||||
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|tcp 127.0.0.1:6791: bind: address already in use" # so-elastic-fleet agent restarting. Seen starting w/ 8.18.8 https://github.com/elastic/kibana/issues/201459
|
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|tcp 127.0.0.1:6791: bind: address already in use" # so-elastic-fleet agent restarting. Seen starting w/ 8.18.8 https://github.com/elastic/kibana/issues/201459
|
||||||
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|TransformTask\] \[logs-(tychon|aws_billing|microsoft_defender_endpoint|armis|o365_metrics|microsoft_sentinel|snyk|cyera|island_browser).*user so_kibana lacks the required permissions \[(logs|metrics)-\1" # Known issue with integrations starting transform jobs that are explicitly not allowed to start as a system user. This error should not be seen on fresh ES 9.3.3 installs or after SO 3.1.0 with soups addition of check_transform_health_and_reauthorize()
|
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|TransformTask\] \[logs-(tychon|aws_billing|microsoft_defender_endpoint).*user so_kibana lacks the required permissions \[logs-\1" # Known issue with 3 integrations using kibana_system role vs creating unique api creds with proper permissions.
|
||||||
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|manifest unknown" # appears in so-dockerregistry log for so-tcpreplay following docker upgrade to 29.2.1-1
|
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|manifest unknown" # appears in so-dockerregistry log for so-tcpreplay following docker upgrade to 29.2.1-1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -42,21 +42,6 @@ clean() {
|
|||||||
# done
|
# done
|
||||||
#fi
|
#fi
|
||||||
|
|
||||||
## Clean up Zeek extracted files processed by Strelka
|
|
||||||
STRELKA_FILES='/nsm/strelka/processed'
|
|
||||||
OLDEST_STRELKA=$(find $STRELKA_FILES -type f -printf '%T+ %p\n' | sort -n | head -n 1)
|
|
||||||
if [ -z "$OLDEST_STRELKA" -o "$OLDEST_STRELKA" == ".." -o "$OLDEST_STRELKA" == "." ]; then
|
|
||||||
echo "$(date) - No old files available to clean up in $STRELKA_FILES" >>$LOG
|
|
||||||
else
|
|
||||||
OLDEST_STRELKA_DATE=$(echo $OLDEST_STRELKA | awk '{print $1}' | cut -d+ -f1)
|
|
||||||
OLDEST_STRELKA_FILE=$(echo $OLDEST_STRELKA | awk '{print $2}')
|
|
||||||
echo "$(date) - Removing extracted files for $OLDEST_STRELKA_DATE" >>$LOG
|
|
||||||
find $STRELKA_FILES -type f -printf '%T+ %p\n' | grep $OLDEST_STRELKA_DATE | awk '{print $2}' | while read FILE; do
|
|
||||||
echo "$(date) - Removing file: $FILE" >>$LOG
|
|
||||||
rm -f "$FILE"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
## Clean up Suricata log files
|
## Clean up Suricata log files
|
||||||
SURICATA_LOGS='/nsm/suricata'
|
SURICATA_LOGS='/nsm/suricata'
|
||||||
OLDEST_SURICATA=$(find $SURICATA_LOGS -type f -printf '%T+ %p\n' | sort -n | head -n 1)
|
OLDEST_SURICATA=$(find $SURICATA_LOGS -type f -printf '%T+ %p\n' | sort -n | head -n 1)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
. /usr/sbin/so-common
|
. /usr/sbin/so-common
|
||||||
|
|
||||||
software_raid=("SOSMN" "SOSMN-DE02" "SOSSNNV" "SOSSNNV-DE02" "SOS10k-DE02" "SOS10KNV" "SOS10KNV-DE02" "SOS10KNV-DE02" "SOS2000-DE02" "SOS-GOFAST-LT-DE02" "SOS-GOFAST-MD-DE02" "SOS-GOFAST-HV-DE02" "HVGUEST")
|
software_raid=("SOSMN" "SOSMN-DE02" "SOSSNNV" "SOSSNNV-DE02" "SOS10k-DE02" "SOS10KNV" "SOS10KNV-DE02" "SOS10KNV-DE02" "SOS2000-DE02" "SOS-GOFAST-LT-DE02" "SOS-GOFAST-MD-DE02" "SOS-GOFAST-HV-DE02")
|
||||||
hardware_raid=("SOS1000" "SOS1000F" "SOSSN7200" "SOS5000" "SOS4000")
|
hardware_raid=("SOS1000" "SOS1000F" "SOSSN7200" "SOS5000" "SOS4000")
|
||||||
|
|
||||||
{%- if salt['grains.get']('sosmodel', '') %}
|
{%- if salt['grains.get']('sosmodel', '') %}
|
||||||
@@ -87,11 +87,6 @@ check_boss_raid() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
check_software_raid() {
|
check_software_raid() {
|
||||||
if [[ ! -f /proc/mdstat ]]; then
|
|
||||||
SWRAID=0
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
SWRC=$(grep "_" /proc/mdstat)
|
SWRC=$(grep "_" /proc/mdstat)
|
||||||
if [[ -n $SWRC ]]; then
|
if [[ -n $SWRC ]]; then
|
||||||
# RAID is failed in some way
|
# RAID is failed in some way
|
||||||
@@ -112,9 +107,7 @@ if [[ "$is_hwraid" == "true" ]]; then
|
|||||||
fi
|
fi
|
||||||
if [[ "$is_softwareraid" == "true" ]]; then
|
if [[ "$is_softwareraid" == "true" ]]; then
|
||||||
check_software_raid
|
check_software_raid
|
||||||
if [ "$model" != "HVGUEST" ]; then
|
check_boss_raid
|
||||||
check_boss_raid
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sum=$(($SWRAID + $BOSSRAID + $HWRAID))
|
sum=$(($SWRAID + $BOSSRAID + $HWRAID))
|
||||||
|
|||||||
@@ -134,48 +134,6 @@ docker:
|
|||||||
extra_hosts: []
|
extra_hosts: []
|
||||||
extra_env: []
|
extra_env: []
|
||||||
ulimits: []
|
ulimits: []
|
||||||
'so-strelka-backend':
|
|
||||||
final_octet: 36
|
|
||||||
custom_bind_mounts: []
|
|
||||||
extra_hosts: []
|
|
||||||
extra_env: []
|
|
||||||
ulimits: []
|
|
||||||
'so-strelka-filestream':
|
|
||||||
final_octet: 37
|
|
||||||
custom_bind_mounts: []
|
|
||||||
extra_hosts: []
|
|
||||||
extra_env: []
|
|
||||||
ulimits: []
|
|
||||||
'so-strelka-frontend':
|
|
||||||
final_octet: 38
|
|
||||||
port_bindings:
|
|
||||||
- 0.0.0.0:57314:57314
|
|
||||||
custom_bind_mounts: []
|
|
||||||
extra_hosts: []
|
|
||||||
extra_env: []
|
|
||||||
ulimits: []
|
|
||||||
'so-strelka-manager':
|
|
||||||
final_octet: 39
|
|
||||||
custom_bind_mounts: []
|
|
||||||
extra_hosts: []
|
|
||||||
extra_env: []
|
|
||||||
ulimits: []
|
|
||||||
'so-strelka-gatekeeper':
|
|
||||||
final_octet: 40
|
|
||||||
port_bindings:
|
|
||||||
- 0.0.0.0:6381:6379
|
|
||||||
custom_bind_mounts: []
|
|
||||||
extra_hosts: []
|
|
||||||
extra_env: []
|
|
||||||
ulimits: []
|
|
||||||
'so-strelka-coordinator':
|
|
||||||
final_octet: 41
|
|
||||||
port_bindings:
|
|
||||||
- 0.0.0.0:6380:6379
|
|
||||||
custom_bind_mounts: []
|
|
||||||
extra_hosts: []
|
|
||||||
extra_env: []
|
|
||||||
ulimits: []
|
|
||||||
'so-elastalert':
|
'so-elastalert':
|
||||||
final_octet: 42
|
final_octet: 42
|
||||||
custom_bind_mounts: []
|
custom_bind_mounts: []
|
||||||
@@ -237,11 +195,3 @@ docker:
|
|||||||
extra_hosts: []
|
extra_hosts: []
|
||||||
extra_env: []
|
extra_env: []
|
||||||
ulimits: []
|
ulimits: []
|
||||||
'so-postgres':
|
|
||||||
final_octet: 47
|
|
||||||
port_bindings:
|
|
||||||
- 0.0.0.0:5432:5432
|
|
||||||
custom_bind_mounts: []
|
|
||||||
extra_hosts: []
|
|
||||||
extra_env: []
|
|
||||||
ulimits: []
|
|
||||||
|
|||||||
@@ -89,12 +89,6 @@ docker:
|
|||||||
so-redis: *dockerOptions
|
so-redis: *dockerOptions
|
||||||
so-sensoroni: *dockerOptions
|
so-sensoroni: *dockerOptions
|
||||||
so-soc: *dockerOptions
|
so-soc: *dockerOptions
|
||||||
so-strelka-backend: *dockerOptions
|
|
||||||
so-strelka-filestream: *dockerOptions
|
|
||||||
so-strelka-frontend: *dockerOptions
|
|
||||||
so-strelka-manager: *dockerOptions
|
|
||||||
so-strelka-gatekeeper: *dockerOptions
|
|
||||||
so-strelka-coordinator: *dockerOptions
|
|
||||||
so-elastalert: *dockerOptions
|
so-elastalert: *dockerOptions
|
||||||
so-elastic-fleet-package-registry: *dockerOptions
|
so-elastic-fleet-package-registry: *dockerOptions
|
||||||
so-idh: *dockerOptions
|
so-idh: *dockerOptions
|
||||||
|
|||||||
@@ -51,16 +51,6 @@ so-elastic-fleet-package-registry:
|
|||||||
- {{ ULIMIT.name }}={{ ULIMIT.soft }}:{{ ULIMIT.hard }}
|
- {{ ULIMIT.name }}={{ ULIMIT.soft }}:{{ ULIMIT.hard }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
wait_for_so-elastic-fleet-package-registry:
|
|
||||||
http.wait_for_successful_query:
|
|
||||||
- name: "http://localhost:8080/health"
|
|
||||||
- status: 200
|
|
||||||
- wait_for: 300
|
|
||||||
- request_interval: 15
|
|
||||||
- require:
|
|
||||||
- docker_container: so-elastic-fleet-package-registry
|
|
||||||
|
|
||||||
delete_so-elastic-fleet-package-registry_so-status.disabled:
|
delete_so-elastic-fleet-package-registry_so-status.disabled:
|
||||||
file.uncomment:
|
file.uncomment:
|
||||||
- name: /opt/so/conf/so-status/so-status.conf
|
- name: /opt/so/conf/so-status/so-status.conf
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
{# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
|
|
||||||
or more contributor license agreements. Licensed under the Elastic License 2.0; you may not use
|
|
||||||
this file except in compliance with the Elastic License 2.0. #}
|
|
||||||
|
|
||||||
|
|
||||||
{% import_json '/opt/so/state/esfleet_content_package_components.json' as ADDON_CONTENT_PACKAGE_COMPONENTS %}
|
|
||||||
{% import_json '/opt/so/state/esfleet_component_templates.json' as INSTALLED_COMPONENT_TEMPLATES %}
|
|
||||||
{% import_yaml 'elasticfleet/defaults.yaml' as ELASTICFLEETDEFAULTS %}
|
|
||||||
|
|
||||||
{% set CORE_ESFLEET_PACKAGES = ELASTICFLEETDEFAULTS.get('elasticfleet', {}).get('packages', {}) %}
|
|
||||||
{% set ADDON_CONTENT_INTEGRATION_DEFAULTS = {} %}
|
|
||||||
{% set DEBUG_STUFF = {} %}
|
|
||||||
|
|
||||||
{% for pkg in ADDON_CONTENT_PACKAGE_COMPONENTS %}
|
|
||||||
{% if pkg.name in CORE_ESFLEET_PACKAGES %}
|
|
||||||
{# skip core content packages #}
|
|
||||||
{% elif pkg.name not in CORE_ESFLEET_PACKAGES %}
|
|
||||||
{# generate defaults for each content package #}
|
|
||||||
{% if pkg.dataStreams is defined and pkg.dataStreams is not none and pkg.dataStreams | length > 0%}
|
|
||||||
{% for pattern in pkg.dataStreams %}
|
|
||||||
{# in ES 9.3.2 'input' type integrations no longer create default component templates and instead they wait for user input during 'integration' setup (fleet ui config)
|
|
||||||
title: generic is an artifact of that and is not in use #}
|
|
||||||
{% if pattern.title == "generic" %}
|
|
||||||
{% continue %}
|
|
||||||
{% endif %}
|
|
||||||
{% if "metrics-" in pattern.name %}
|
|
||||||
{% set integration_type = "metrics-" %}
|
|
||||||
{% elif "logs-" in pattern.name %}
|
|
||||||
{% set integration_type = "logs-" %}
|
|
||||||
{% else %}
|
|
||||||
{% set integration_type = "" %}
|
|
||||||
{% endif %}
|
|
||||||
{# on content integrations the component name is user defined at the time it is added to an agent policy #}
|
|
||||||
{% set component_name = pattern.title %}
|
|
||||||
{% set index_pattern = pattern.name %}
|
|
||||||
{# component_name_x maintains the functionality of merging local pillar changes with generated 'defaults' via SOC UI #}
|
|
||||||
{% set component_name_x = component_name.replace(".","_x_") %}
|
|
||||||
{# pillar overrides/merge expects the key names to follow the naming in elasticsearch/defaults.yaml eg. so-logs-1password_x_item_usages . The _x_ is replaced later on in elasticsearch/template.map.jinja #}
|
|
||||||
{% set integration_key = "so-" ~ integration_type ~ pkg.name + '_x_' ~ component_name_x %}
|
|
||||||
{# Default integration settings #}
|
|
||||||
{% set integration_defaults = {
|
|
||||||
"index_sorting": false,
|
|
||||||
"index_template": {
|
|
||||||
"composed_of": [integration_type ~ component_name ~ "@package", integration_type ~ component_name ~ "@custom", "so-fleet_integrations.ip_mappings-1", "so-fleet_globals-1", "so-fleet_agent_id_verification-1"],
|
|
||||||
"data_stream": {
|
|
||||||
"allow_custom_routing": false,
|
|
||||||
"hidden": false
|
|
||||||
},
|
|
||||||
"ignore_missing_component_templates": [integration_type ~ component_name ~ "@custom"],
|
|
||||||
"index_patterns": [index_pattern],
|
|
||||||
"priority": 501,
|
|
||||||
"template": {
|
|
||||||
"settings": {
|
|
||||||
"index": {
|
|
||||||
"lifecycle": {"name": "so-" ~ integration_type ~ component_name ~ "-logs"},
|
|
||||||
"number_of_replicas": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"policy": {
|
|
||||||
"phases": {
|
|
||||||
"cold": {
|
|
||||||
"actions": {
|
|
||||||
"allocate":{
|
|
||||||
"number_of_replicas": ""
|
|
||||||
},
|
|
||||||
"set_priority": {"priority": 0}
|
|
||||||
},
|
|
||||||
"min_age": "60d"
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
"actions": {
|
|
||||||
"delete": {}
|
|
||||||
},
|
|
||||||
"min_age": "365d"
|
|
||||||
},
|
|
||||||
"hot": {
|
|
||||||
"actions": {
|
|
||||||
"rollover": {
|
|
||||||
"max_age": "30d",
|
|
||||||
"max_primary_shard_size": "50gb"
|
|
||||||
},
|
|
||||||
"forcemerge":{
|
|
||||||
"max_num_segments": ""
|
|
||||||
},
|
|
||||||
"shrink":{
|
|
||||||
"max_primary_shard_size": "",
|
|
||||||
"method": "COUNT",
|
|
||||||
"number_of_shards": ""
|
|
||||||
},
|
|
||||||
"set_priority": {"priority": 100}
|
|
||||||
},
|
|
||||||
"min_age": "0ms"
|
|
||||||
},
|
|
||||||
"warm": {
|
|
||||||
"actions": {
|
|
||||||
"allocate": {
|
|
||||||
"number_of_replicas": ""
|
|
||||||
},
|
|
||||||
"forcemerge": {
|
|
||||||
"max_num_segments": ""
|
|
||||||
},
|
|
||||||
"shrink":{
|
|
||||||
"max_primary_shard_size": "",
|
|
||||||
"method": "COUNT",
|
|
||||||
"number_of_shards": ""
|
|
||||||
},
|
|
||||||
"set_priority": {"priority": 50}
|
|
||||||
},
|
|
||||||
"min_age": "30d"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} %}
|
|
||||||
|
|
||||||
|
|
||||||
{% do ADDON_CONTENT_INTEGRATION_DEFAULTS.update({integration_key: integration_defaults}) %}
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
elasticfleet:
|
elasticfleet:
|
||||||
enabled: False
|
enabled: False
|
||||||
patch_version: 9.3.3+build202604082258 # Elastic Agent specific patch release.
|
|
||||||
enable_manager_output: True
|
enable_manager_output: True
|
||||||
config:
|
config:
|
||||||
server:
|
server:
|
||||||
|
|||||||
@@ -17,19 +17,65 @@ include:
|
|||||||
- logstash.ssl
|
- logstash.ssl
|
||||||
- elasticfleet.config
|
- elasticfleet.config
|
||||||
- elasticfleet.sostatus
|
- elasticfleet.sostatus
|
||||||
{%- if GLOBALS.role != "so-fleet" %}
|
|
||||||
- elasticfleet.manager
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
{% if GLOBALS.role != "so-fleet" %}
|
{% if grains.role not in ['so-fleet'] %}
|
||||||
# Wait for Elasticsearch to be ready - no reason to try running Elastic Fleet server if ES is not ready
|
# Wait for Elasticsearch to be ready - no reason to try running Elastic Fleet server if ES is not ready
|
||||||
wait_for_elasticsearch_elasticfleet:
|
wait_for_elasticsearch_elasticfleet:
|
||||||
cmd.run:
|
cmd.run:
|
||||||
- name: so-elasticsearch-wait
|
- name: so-elasticsearch-wait
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if GLOBALS.role == "so-fleet" %}
|
# If enabled, automatically update Fleet Logstash Outputs
|
||||||
|
{% if ELASTICFLEETMERGED.config.server.enable_auto_configuration and grains.role not in ['so-import', 'so-eval', 'so-fleet'] %}
|
||||||
|
so-elastic-fleet-auto-configure-logstash-outputs:
|
||||||
|
cmd.run:
|
||||||
|
- name: /usr/sbin/so-elastic-fleet-outputs-update
|
||||||
|
- retry:
|
||||||
|
attempts: 4
|
||||||
|
interval: 30
|
||||||
|
|
||||||
|
{# Separate from above in order to catch elasticfleet-logstash.crt changes and force update to fleet output policy #}
|
||||||
|
so-elastic-fleet-auto-configure-logstash-outputs-force:
|
||||||
|
cmd.run:
|
||||||
|
- name: /usr/sbin/so-elastic-fleet-outputs-update --certs
|
||||||
|
- retry:
|
||||||
|
attempts: 4
|
||||||
|
interval: 30
|
||||||
|
- onchanges:
|
||||||
|
- x509: etc_elasticfleet_logstash_crt
|
||||||
|
- x509: elasticfleet_kafka_crt
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
# If enabled, automatically update Fleet Server URLs & ES Connection
|
||||||
|
{% if ELASTICFLEETMERGED.config.server.enable_auto_configuration and grains.role not in ['so-fleet'] %}
|
||||||
|
so-elastic-fleet-auto-configure-server-urls:
|
||||||
|
cmd.run:
|
||||||
|
- name: /usr/sbin/so-elastic-fleet-urls-update
|
||||||
|
- retry:
|
||||||
|
attempts: 4
|
||||||
|
interval: 30
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
# Automatically update Fleet Server Elasticsearch URLs & Agent Artifact URLs
|
||||||
|
{% if grains.role not in ['so-fleet'] %}
|
||||||
|
so-elastic-fleet-auto-configure-elasticsearch-urls:
|
||||||
|
cmd.run:
|
||||||
|
- name: /usr/sbin/so-elastic-fleet-es-url-update
|
||||||
|
- retry:
|
||||||
|
attempts: 4
|
||||||
|
interval: 30
|
||||||
|
|
||||||
|
so-elastic-fleet-auto-configure-artifact-urls:
|
||||||
|
cmd.run:
|
||||||
|
- name: /usr/sbin/so-elastic-fleet-artifacts-url-update
|
||||||
|
- retry:
|
||||||
|
attempts: 4
|
||||||
|
interval: 30
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
# Sync Elastic Agent artifacts to Fleet Node
|
# Sync Elastic Agent artifacts to Fleet Node
|
||||||
|
{% if grains.role in ['so-fleet'] %}
|
||||||
elasticagent_syncartifacts:
|
elasticagent_syncartifacts:
|
||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /nsm/elastic-fleet/artifacts/beats
|
- name: /nsm/elastic-fleet/artifacts/beats
|
||||||
@@ -103,6 +149,57 @@ so-elastic-fleet:
|
|||||||
- x509: etc_elasticfleet_crt
|
- x509: etc_elasticfleet_crt
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if GLOBALS.role != "so-fleet" %}
|
||||||
|
so-elastic-fleet-package-statefile:
|
||||||
|
file.managed:
|
||||||
|
- name: /opt/so/state/elastic_fleet_packages.txt
|
||||||
|
- contents: {{ELASTICFLEETMERGED.packages}}
|
||||||
|
|
||||||
|
so-elastic-fleet-package-upgrade:
|
||||||
|
cmd.run:
|
||||||
|
- name: /usr/sbin/so-elastic-fleet-package-upgrade
|
||||||
|
- retry:
|
||||||
|
attempts: 3
|
||||||
|
interval: 10
|
||||||
|
- onchanges:
|
||||||
|
- file: /opt/so/state/elastic_fleet_packages.txt
|
||||||
|
|
||||||
|
so-elastic-fleet-integrations:
|
||||||
|
cmd.run:
|
||||||
|
- name: /usr/sbin/so-elastic-fleet-integration-policy-load
|
||||||
|
- retry:
|
||||||
|
attempts: 3
|
||||||
|
interval: 10
|
||||||
|
|
||||||
|
so-elastic-agent-grid-upgrade:
|
||||||
|
cmd.run:
|
||||||
|
- name: /usr/sbin/so-elastic-agent-grid-upgrade
|
||||||
|
- retry:
|
||||||
|
attempts: 12
|
||||||
|
interval: 5
|
||||||
|
|
||||||
|
so-elastic-fleet-integration-upgrade:
|
||||||
|
cmd.run:
|
||||||
|
- name: /usr/sbin/so-elastic-fleet-integration-upgrade
|
||||||
|
- retry:
|
||||||
|
attempts: 3
|
||||||
|
interval: 10
|
||||||
|
|
||||||
|
{# Optional integrations script doesn't need the retries like so-elastic-fleet-integration-upgrade which loads the default integrations #}
|
||||||
|
so-elastic-fleet-addon-integrations:
|
||||||
|
cmd.run:
|
||||||
|
- name: /usr/sbin/so-elastic-fleet-optional-integrations-load
|
||||||
|
|
||||||
|
{% if ELASTICFLEETMERGED.config.defend_filters.enable_auto_configuration %}
|
||||||
|
so-elastic-defend-manage-filters-file-watch:
|
||||||
|
cmd.run:
|
||||||
|
- name: python3 /sbin/so-elastic-defend-manage-filters.py -c /opt/so/conf/elasticsearch/curl.config -d /opt/so/conf/elastic-fleet/defend-exclusions/disabled-filters.yaml -i /nsm/securityonion-resources/event_filters/ -i /opt/so/conf/elastic-fleet/defend-exclusions/rulesets/custom-filters/ &>> /opt/so/log/elasticfleet/elastic-defend-manage-filters.log
|
||||||
|
- onchanges:
|
||||||
|
- file: elasticdefendcustom
|
||||||
|
- file: elasticdefenddisabled
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
delete_so-elastic-fleet_so-status.disabled:
|
delete_so-elastic-fleet_so-status.disabled:
|
||||||
file.uncomment:
|
file.uncomment:
|
||||||
- name: /opt/so/conf/so-status/so-status.conf
|
- name: /opt/so/conf/so-status/so-status.conf
|
||||||
|
|||||||
+2
-9
@@ -9,22 +9,16 @@
|
|||||||
"namespace": "so",
|
"namespace": "so",
|
||||||
"description": "Zeek Import logs",
|
"description": "Zeek Import logs",
|
||||||
"policy_id": "so-grid-nodes_general",
|
"policy_id": "so-grid-nodes_general",
|
||||||
"policy_ids": [
|
|
||||||
"so-grid-nodes_general"
|
|
||||||
],
|
|
||||||
"vars": {},
|
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"filestream-filestream": {
|
"filestream-filestream": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"streams": {
|
"streams": {
|
||||||
"filestream.filestream": {
|
"filestream.generic": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"vars": {
|
"vars": {
|
||||||
"paths": [
|
"paths": [
|
||||||
"/nsm/import/*/zeek/logs/*.log"
|
"/nsm/import/*/zeek/logs/*.log"
|
||||||
],
|
],
|
||||||
"compression_gzip": false,
|
|
||||||
"use_logs_stream": false,
|
|
||||||
"data_stream.dataset": "import",
|
"data_stream.dataset": "import",
|
||||||
"pipeline": "",
|
"pipeline": "",
|
||||||
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
||||||
@@ -40,8 +34,7 @@
|
|||||||
"fingerprint_length": "64",
|
"fingerprint_length": "64",
|
||||||
"file_identity_native": true,
|
"file_identity_native": true,
|
||||||
"exclude_lines": [],
|
"exclude_lines": [],
|
||||||
"include_lines": [],
|
"include_lines": []
|
||||||
"delete_enabled": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,25 +15,19 @@
|
|||||||
"version": ""
|
"version": ""
|
||||||
},
|
},
|
||||||
"name": "kratos-logs",
|
"name": "kratos-logs",
|
||||||
"namespace": "so",
|
|
||||||
"description": "Kratos logs",
|
"description": "Kratos logs",
|
||||||
"policy_id": "so-grid-nodes_general",
|
"policy_id": "so-grid-nodes_general",
|
||||||
"policy_ids": [
|
"namespace": "so",
|
||||||
"so-grid-nodes_general"
|
|
||||||
],
|
|
||||||
"vars": {},
|
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"filestream-filestream": {
|
"filestream-filestream": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"streams": {
|
"streams": {
|
||||||
"filestream.filestream": {
|
"filestream.generic": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"vars": {
|
"vars": {
|
||||||
"paths": [
|
"paths": [
|
||||||
"/opt/so/log/kratos/kratos.log"
|
"/opt/so/log/kratos/kratos.log"
|
||||||
],
|
],
|
||||||
"compression_gzip": false,
|
|
||||||
"use_logs_stream": false,
|
|
||||||
"data_stream.dataset": "kratos",
|
"data_stream.dataset": "kratos",
|
||||||
"pipeline": "kratos",
|
"pipeline": "kratos",
|
||||||
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
||||||
@@ -54,10 +48,10 @@
|
|||||||
"harvester_limit": 0,
|
"harvester_limit": 0,
|
||||||
"fingerprint": false,
|
"fingerprint": false,
|
||||||
"fingerprint_offset": 0,
|
"fingerprint_offset": 0,
|
||||||
|
"fingerprint_length": "64",
|
||||||
"file_identity_native": true,
|
"file_identity_native": true,
|
||||||
"exclude_lines": [],
|
"exclude_lines": [],
|
||||||
"include_lines": [],
|
"include_lines": []
|
||||||
"delete_enabled": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,22 +9,16 @@
|
|||||||
"namespace": "so",
|
"namespace": "so",
|
||||||
"description": "Zeek logs",
|
"description": "Zeek logs",
|
||||||
"policy_id": "so-grid-nodes_general",
|
"policy_id": "so-grid-nodes_general",
|
||||||
"policy_ids": [
|
|
||||||
"so-grid-nodes_general"
|
|
||||||
],
|
|
||||||
"vars": {},
|
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"filestream-filestream": {
|
"filestream-filestream": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"streams": {
|
"streams": {
|
||||||
"filestream.filestream": {
|
"filestream.generic": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"vars": {
|
"vars": {
|
||||||
"paths": [
|
"paths": [
|
||||||
"/nsm/zeek/logs/current/*.log"
|
"/nsm/zeek/logs/current/*.log"
|
||||||
],
|
],
|
||||||
"compression_gzip": false,
|
|
||||||
"use_logs_stream": false,
|
|
||||||
"data_stream.dataset": "zeek",
|
"data_stream.dataset": "zeek",
|
||||||
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
||||||
"exclude_files": ["({%- endraw -%}{{ ELASTICFLEETMERGED.logging.zeek.excluded | join('|') }}{%- raw -%})(\\..+)?\\.log$"],
|
"exclude_files": ["({%- endraw -%}{{ ELASTICFLEETMERGED.logging.zeek.excluded | join('|') }}{%- raw -%})(\\..+)?\\.log$"],
|
||||||
@@ -36,10 +30,10 @@
|
|||||||
"harvester_limit": 0,
|
"harvester_limit": 0,
|
||||||
"fingerprint": false,
|
"fingerprint": false,
|
||||||
"fingerprint_offset": 0,
|
"fingerprint_offset": 0,
|
||||||
|
"fingerprint_length": "64",
|
||||||
"file_identity_native": true,
|
"file_identity_native": true,
|
||||||
"exclude_lines": [],
|
"exclude_lines": [],
|
||||||
"include_lines": [],
|
"include_lines": []
|
||||||
"delete_enabled": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"package": {
|
"package": {
|
||||||
"name": "endpoint",
|
"name": "endpoint",
|
||||||
"title": "Elastic Defend",
|
"title": "Elastic Defend",
|
||||||
"version": "9.3.0",
|
"version": "9.0.2",
|
||||||
"requires_root": true
|
"requires_root": true
|
||||||
},
|
},
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
|||||||
@@ -6,23 +6,21 @@
|
|||||||
"name": "agent-monitor",
|
"name": "agent-monitor",
|
||||||
"namespace": "",
|
"namespace": "",
|
||||||
"description": "",
|
"description": "",
|
||||||
"policy_id": "so-grid-nodes_general",
|
|
||||||
"policy_ids": [
|
"policy_ids": [
|
||||||
"so-grid-nodes_general"
|
"so-grid-nodes_general"
|
||||||
],
|
],
|
||||||
|
"output_id": null,
|
||||||
"vars": {},
|
"vars": {},
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"filestream-filestream": {
|
"filestream-filestream": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"streams": {
|
"streams": {
|
||||||
"filestream.filestream": {
|
"filestream.generic": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"vars": {
|
"vars": {
|
||||||
"paths": [
|
"paths": [
|
||||||
"/opt/so/log/agents/agent-monitor.log"
|
"/opt/so/log/agents/agent-monitor.log"
|
||||||
],
|
],
|
||||||
"compression_gzip": false,
|
|
||||||
"use_logs_stream": false,
|
|
||||||
"data_stream.dataset": "agentmonitor",
|
"data_stream.dataset": "agentmonitor",
|
||||||
"pipeline": "elasticagent.monitor",
|
"pipeline": "elasticagent.monitor",
|
||||||
"parsers": "",
|
"parsers": "",
|
||||||
@@ -36,16 +34,15 @@
|
|||||||
"ignore_older": "72h",
|
"ignore_older": "72h",
|
||||||
"clean_inactive": -1,
|
"clean_inactive": -1,
|
||||||
"harvester_limit": 0,
|
"harvester_limit": 0,
|
||||||
"fingerprint": false,
|
"fingerprint": true,
|
||||||
"fingerprint_offset": 0,
|
"fingerprint_offset": 0,
|
||||||
"file_identity_native": true,
|
"fingerprint_length": 64,
|
||||||
|
"file_identity_native": false,
|
||||||
"exclude_lines": [],
|
"exclude_lines": [],
|
||||||
"include_lines": [],
|
"include_lines": []
|
||||||
"delete_enabled": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"force": true
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,25 +4,19 @@
|
|||||||
"version": ""
|
"version": ""
|
||||||
},
|
},
|
||||||
"name": "hydra-logs",
|
"name": "hydra-logs",
|
||||||
"namespace": "so",
|
|
||||||
"description": "Hydra logs",
|
"description": "Hydra logs",
|
||||||
"policy_id": "so-grid-nodes_general",
|
"policy_id": "so-grid-nodes_general",
|
||||||
"policy_ids": [
|
"namespace": "so",
|
||||||
"so-grid-nodes_general"
|
|
||||||
],
|
|
||||||
"vars": {},
|
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"filestream-filestream": {
|
"filestream-filestream": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"streams": {
|
"streams": {
|
||||||
"filestream.filestream": {
|
"filestream.generic": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"vars": {
|
"vars": {
|
||||||
"paths": [
|
"paths": [
|
||||||
"/opt/so/log/hydra/hydra.log"
|
"/opt/so/log/hydra/hydra.log"
|
||||||
],
|
],
|
||||||
"compression_gzip": false,
|
|
||||||
"use_logs_stream": false,
|
|
||||||
"data_stream.dataset": "hydra",
|
"data_stream.dataset": "hydra",
|
||||||
"pipeline": "hydra",
|
"pipeline": "hydra",
|
||||||
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
||||||
@@ -40,10 +34,10 @@
|
|||||||
"harvester_limit": 0,
|
"harvester_limit": 0,
|
||||||
"fingerprint": false,
|
"fingerprint": false,
|
||||||
"fingerprint_offset": 0,
|
"fingerprint_offset": 0,
|
||||||
|
"fingerprint_length": "64",
|
||||||
"file_identity_native": true,
|
"file_identity_native": true,
|
||||||
"exclude_lines": [],
|
"exclude_lines": [],
|
||||||
"include_lines": [],
|
"include_lines": []
|
||||||
"delete_enabled": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,25 +4,19 @@
|
|||||||
"version": ""
|
"version": ""
|
||||||
},
|
},
|
||||||
"name": "idh-logs",
|
"name": "idh-logs",
|
||||||
"namespace": "so",
|
|
||||||
"description": "IDH integration",
|
"description": "IDH integration",
|
||||||
"policy_id": "so-grid-nodes_general",
|
"policy_id": "so-grid-nodes_general",
|
||||||
"policy_ids": [
|
"namespace": "so",
|
||||||
"so-grid-nodes_general"
|
|
||||||
],
|
|
||||||
"vars": {},
|
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"filestream-filestream": {
|
"filestream-filestream": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"streams": {
|
"streams": {
|
||||||
"filestream.filestream": {
|
"filestream.generic": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"vars": {
|
"vars": {
|
||||||
"paths": [
|
"paths": [
|
||||||
"/nsm/idh/opencanary.log"
|
"/nsm/idh/opencanary.log"
|
||||||
],
|
],
|
||||||
"compression_gzip": false,
|
|
||||||
"use_logs_stream": false,
|
|
||||||
"data_stream.dataset": "idh",
|
"data_stream.dataset": "idh",
|
||||||
"pipeline": "common",
|
"pipeline": "common",
|
||||||
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
||||||
@@ -37,10 +31,10 @@
|
|||||||
"harvester_limit": 0,
|
"harvester_limit": 0,
|
||||||
"fingerprint": false,
|
"fingerprint": false,
|
||||||
"fingerprint_offset": 0,
|
"fingerprint_offset": 0,
|
||||||
|
"fingerprint_length": "64",
|
||||||
"file_identity_native": true,
|
"file_identity_native": true,
|
||||||
"exclude_lines": [],
|
"exclude_lines": [],
|
||||||
"include_lines": [],
|
"include_lines": []
|
||||||
"delete_enabled": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,32 +4,26 @@
|
|||||||
"version": ""
|
"version": ""
|
||||||
},
|
},
|
||||||
"name": "import-evtx-logs",
|
"name": "import-evtx-logs",
|
||||||
"namespace": "so",
|
|
||||||
"description": "Import Windows EVTX logs",
|
"description": "Import Windows EVTX logs",
|
||||||
"policy_id": "so-grid-nodes_general",
|
"policy_id": "so-grid-nodes_general",
|
||||||
"policy_ids": [
|
"namespace": "so",
|
||||||
"so-grid-nodes_general"
|
|
||||||
],
|
|
||||||
"vars": {},
|
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"filestream-filestream": {
|
"filestream-filestream": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"streams": {
|
"streams": {
|
||||||
"filestream.filestream": {
|
"filestream.generic": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"vars": {
|
"vars": {
|
||||||
"paths": [
|
"paths": [
|
||||||
"/nsm/import/*/evtx/*.json"
|
"/nsm/import/*/evtx/*.json"
|
||||||
],
|
],
|
||||||
"compression_gzip": false,
|
|
||||||
"use_logs_stream": false,
|
|
||||||
"data_stream.dataset": "import",
|
"data_stream.dataset": "import",
|
||||||
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
||||||
"exclude_files": [
|
"exclude_files": [
|
||||||
"\\.gz$"
|
"\\.gz$"
|
||||||
],
|
],
|
||||||
"include_files": [],
|
"include_files": [],
|
||||||
"processors": "- dissect:\n tokenizer: \"/nsm/import/%{import.id}/evtx/%{import.file}\"\n field: \"log.file.path\"\n target_prefix: \"\"\n- decode_json_fields:\n fields: [\"message\"]\n target: \"\"\n- drop_fields:\n fields: [\"host\"]\n ignore_missing: true\n- add_fields:\n target: data_stream\n fields:\n type: logs\n dataset: system.security\n- add_fields:\n target: event\n fields:\n dataset: system.security\n module: system\n imported: true\n- add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-system.security-2.15.0\n- if:\n equals:\n winlog.channel: 'Microsoft-Windows-Sysmon/Operational'\n then: \n - add_fields:\n target: data_stream\n fields:\n dataset: windows.sysmon_operational\n - add_fields:\n target: event\n fields:\n dataset: windows.sysmon_operational\n module: windows\n imported: true\n - add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-windows.sysmon_operational-3.8.0\n- if:\n equals:\n winlog.channel: 'Application'\n then: \n - add_fields:\n target: data_stream\n fields:\n dataset: system.application\n - add_fields:\n target: event\n fields:\n dataset: system.application\n - add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-system.application-2.15.0\n- if:\n equals:\n winlog.channel: 'System'\n then: \n - add_fields:\n target: data_stream\n fields:\n dataset: system.system\n - add_fields:\n target: event\n fields:\n dataset: system.system\n - add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-system.system-2.15.0\n \n- if:\n equals:\n winlog.channel: 'Microsoft-Windows-PowerShell/Operational'\n then: \n - add_fields:\n target: data_stream\n fields:\n dataset: windows.powershell_operational\n - add_fields:\n target: event\n fields:\n dataset: windows.powershell_operational\n module: windows\n - add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-windows.powershell_operational-3.8.0\n- add_fields:\n target: data_stream\n fields:\n dataset: import",
|
"processors": "- dissect:\n tokenizer: \"/nsm/import/%{import.id}/evtx/%{import.file}\"\n field: \"log.file.path\"\n target_prefix: \"\"\n- decode_json_fields:\n fields: [\"message\"]\n target: \"\"\n- drop_fields:\n fields: [\"host\"]\n ignore_missing: true\n- add_fields:\n target: data_stream\n fields:\n type: logs\n dataset: system.security\n- add_fields:\n target: event\n fields:\n dataset: system.security\n module: system\n imported: true\n- add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-system.security-2.6.1\n- if:\n equals:\n winlog.channel: 'Microsoft-Windows-Sysmon/Operational'\n then: \n - add_fields:\n target: data_stream\n fields:\n dataset: windows.sysmon_operational\n - add_fields:\n target: event\n fields:\n dataset: windows.sysmon_operational\n module: windows\n imported: true\n - add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-windows.sysmon_operational-3.1.2\n- if:\n equals:\n winlog.channel: 'Application'\n then: \n - add_fields:\n target: data_stream\n fields:\n dataset: system.application\n - add_fields:\n target: event\n fields:\n dataset: system.application\n - add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-system.application-2.6.1\n- if:\n equals:\n winlog.channel: 'System'\n then: \n - add_fields:\n target: data_stream\n fields:\n dataset: system.system\n - add_fields:\n target: event\n fields:\n dataset: system.system\n - add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-system.system-2.6.1\n \n- if:\n equals:\n winlog.channel: 'Microsoft-Windows-PowerShell/Operational'\n then: \n - add_fields:\n target: data_stream\n fields:\n dataset: windows.powershell_operational\n - add_fields:\n target: event\n fields:\n dataset: windows.powershell_operational\n module: windows\n - add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-windows.powershell_operational-3.1.2\n- add_fields:\n target: data_stream\n fields:\n dataset: import",
|
||||||
"tags": [
|
"tags": [
|
||||||
"import"
|
"import"
|
||||||
],
|
],
|
||||||
@@ -39,10 +33,10 @@
|
|||||||
"harvester_limit": 0,
|
"harvester_limit": 0,
|
||||||
"fingerprint": false,
|
"fingerprint": false,
|
||||||
"fingerprint_offset": 0,
|
"fingerprint_offset": 0,
|
||||||
|
"fingerprint_length": "64",
|
||||||
"file_identity_native": true,
|
"file_identity_native": true,
|
||||||
"exclude_lines": [],
|
"exclude_lines": [],
|
||||||
"include_lines": [],
|
"include_lines": []
|
||||||
"delete_enabled": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,25 +4,19 @@
|
|||||||
"version": ""
|
"version": ""
|
||||||
},
|
},
|
||||||
"name": "import-suricata-logs",
|
"name": "import-suricata-logs",
|
||||||
"namespace": "so",
|
|
||||||
"description": "Import Suricata logs",
|
"description": "Import Suricata logs",
|
||||||
"policy_id": "so-grid-nodes_general",
|
"policy_id": "so-grid-nodes_general",
|
||||||
"policy_ids": [
|
"namespace": "so",
|
||||||
"so-grid-nodes_general"
|
|
||||||
],
|
|
||||||
"vars": {},
|
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"filestream-filestream": {
|
"filestream-filestream": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"streams": {
|
"streams": {
|
||||||
"filestream.filestream": {
|
"filestream.generic": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"vars": {
|
"vars": {
|
||||||
"paths": [
|
"paths": [
|
||||||
"/nsm/import/*/suricata/eve*.json"
|
"/nsm/import/*/suricata/eve*.json"
|
||||||
],
|
],
|
||||||
"compression_gzip": false,
|
|
||||||
"use_logs_stream": false,
|
|
||||||
"data_stream.dataset": "import",
|
"data_stream.dataset": "import",
|
||||||
"pipeline": "suricata.common",
|
"pipeline": "suricata.common",
|
||||||
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
||||||
@@ -38,10 +32,10 @@
|
|||||||
"harvester_limit": 0,
|
"harvester_limit": 0,
|
||||||
"fingerprint": false,
|
"fingerprint": false,
|
||||||
"fingerprint_offset": 0,
|
"fingerprint_offset": 0,
|
||||||
|
"fingerprint_length": "64",
|
||||||
"file_identity_native": true,
|
"file_identity_native": true,
|
||||||
"exclude_lines": [],
|
"exclude_lines": [],
|
||||||
"include_lines": [],
|
"include_lines": []
|
||||||
"delete_enabled": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,18 +4,14 @@
|
|||||||
"version": ""
|
"version": ""
|
||||||
},
|
},
|
||||||
"name": "rita-logs",
|
"name": "rita-logs",
|
||||||
"namespace": "so",
|
|
||||||
"description": "RITA Logs",
|
"description": "RITA Logs",
|
||||||
"policy_id": "so-grid-nodes_general",
|
"policy_id": "so-grid-nodes_general",
|
||||||
"policy_ids": [
|
"namespace": "so",
|
||||||
"so-grid-nodes_general"
|
|
||||||
],
|
|
||||||
"vars": {},
|
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"filestream-filestream": {
|
"filestream-filestream": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"streams": {
|
"streams": {
|
||||||
"filestream.filestream": {
|
"filestream.generic": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"vars": {
|
"vars": {
|
||||||
"paths": [
|
"paths": [
|
||||||
@@ -23,8 +19,6 @@
|
|||||||
"/nsm/rita/exploded-dns.csv",
|
"/nsm/rita/exploded-dns.csv",
|
||||||
"/nsm/rita/long-connections.csv"
|
"/nsm/rita/long-connections.csv"
|
||||||
],
|
],
|
||||||
"compression_gzip": false,
|
|
||||||
"use_logs_stream": false,
|
|
||||||
"data_stream.dataset": "rita",
|
"data_stream.dataset": "rita",
|
||||||
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
||||||
"exclude_files": [
|
"exclude_files": [
|
||||||
@@ -39,10 +33,10 @@
|
|||||||
"harvester_limit": 0,
|
"harvester_limit": 0,
|
||||||
"fingerprint": false,
|
"fingerprint": false,
|
||||||
"fingerprint_offset": 0,
|
"fingerprint_offset": 0,
|
||||||
|
"fingerprint_length": "64",
|
||||||
"file_identity_native": true,
|
"file_identity_native": true,
|
||||||
"exclude_lines": [],
|
"exclude_lines": [],
|
||||||
"include_lines": [],
|
"include_lines": []
|
||||||
"delete_enabled": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,25 +4,19 @@
|
|||||||
"version": ""
|
"version": ""
|
||||||
},
|
},
|
||||||
"name": "so-ip-mappings",
|
"name": "so-ip-mappings",
|
||||||
"namespace": "so",
|
|
||||||
"description": "IP Description mappings",
|
"description": "IP Description mappings",
|
||||||
"policy_id": "so-grid-nodes_general",
|
"policy_id": "so-grid-nodes_general",
|
||||||
"policy_ids": [
|
"namespace": "so",
|
||||||
"so-grid-nodes_general"
|
|
||||||
],
|
|
||||||
"vars": {},
|
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"filestream-filestream": {
|
"filestream-filestream": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"streams": {
|
"streams": {
|
||||||
"filestream.filestream": {
|
"filestream.generic": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"vars": {
|
"vars": {
|
||||||
"paths": [
|
"paths": [
|
||||||
"/nsm/custom-mappings/ip-descriptions.csv"
|
"/nsm/custom-mappings/ip-descriptions.csv"
|
||||||
],
|
],
|
||||||
"compression_gzip": false,
|
|
||||||
"use_logs_stream": false,
|
|
||||||
"data_stream.dataset": "hostnamemappings",
|
"data_stream.dataset": "hostnamemappings",
|
||||||
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
||||||
"exclude_files": [
|
"exclude_files": [
|
||||||
@@ -38,10 +32,10 @@
|
|||||||
"harvester_limit": 0,
|
"harvester_limit": 0,
|
||||||
"fingerprint": false,
|
"fingerprint": false,
|
||||||
"fingerprint_offset": 0,
|
"fingerprint_offset": 0,
|
||||||
|
"fingerprint_length": "64",
|
||||||
"file_identity_native": true,
|
"file_identity_native": true,
|
||||||
"exclude_lines": [],
|
"exclude_lines": [],
|
||||||
"include_lines": [],
|
"include_lines": []
|
||||||
"delete_enabled": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,25 +4,19 @@
|
|||||||
"version": ""
|
"version": ""
|
||||||
},
|
},
|
||||||
"name": "soc-auth-sync-logs",
|
"name": "soc-auth-sync-logs",
|
||||||
"namespace": "so",
|
|
||||||
"description": "Security Onion - Elastic Auth Sync - Logs",
|
"description": "Security Onion - Elastic Auth Sync - Logs",
|
||||||
"policy_id": "so-grid-nodes_general",
|
"policy_id": "so-grid-nodes_general",
|
||||||
"policy_ids": [
|
"namespace": "so",
|
||||||
"so-grid-nodes_general"
|
|
||||||
],
|
|
||||||
"vars": {},
|
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"filestream-filestream": {
|
"filestream-filestream": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"streams": {
|
"streams": {
|
||||||
"filestream.filestream": {
|
"filestream.generic": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"vars": {
|
"vars": {
|
||||||
"paths": [
|
"paths": [
|
||||||
"/opt/so/log/soc/sync.log"
|
"/opt/so/log/soc/sync.log"
|
||||||
],
|
],
|
||||||
"compression_gzip": false,
|
|
||||||
"use_logs_stream": false,
|
|
||||||
"data_stream.dataset": "soc",
|
"data_stream.dataset": "soc",
|
||||||
"pipeline": "common",
|
"pipeline": "common",
|
||||||
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
||||||
@@ -37,10 +31,10 @@
|
|||||||
"harvester_limit": 0,
|
"harvester_limit": 0,
|
||||||
"fingerprint": false,
|
"fingerprint": false,
|
||||||
"fingerprint_offset": 0,
|
"fingerprint_offset": 0,
|
||||||
|
"fingerprint_length": "64",
|
||||||
"file_identity_native": true,
|
"file_identity_native": true,
|
||||||
"exclude_lines": [],
|
"exclude_lines": [],
|
||||||
"include_lines": [],
|
"include_lines": []
|
||||||
"delete_enabled": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,26 +4,20 @@
|
|||||||
"version": ""
|
"version": ""
|
||||||
},
|
},
|
||||||
"name": "soc-detections-logs",
|
"name": "soc-detections-logs",
|
||||||
"namespace": "so",
|
|
||||||
"description": "Security Onion Console - Detections Logs",
|
"description": "Security Onion Console - Detections Logs",
|
||||||
"policy_id": "so-grid-nodes_general",
|
"policy_id": "so-grid-nodes_general",
|
||||||
"policy_ids": [
|
"namespace": "so",
|
||||||
"so-grid-nodes_general"
|
|
||||||
],
|
|
||||||
"vars": {},
|
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"filestream-filestream": {
|
"filestream-filestream": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"streams": {
|
"streams": {
|
||||||
"filestream.filestream": {
|
"filestream.generic": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"vars": {
|
"vars": {
|
||||||
"paths": [
|
"paths": [
|
||||||
"/opt/so/log/soc/detections_runtime-status_sigma.log",
|
"/opt/so/log/soc/detections_runtime-status_sigma.log",
|
||||||
"/opt/so/log/soc/detections_runtime-status_yara.log"
|
"/opt/so/log/soc/detections_runtime-status_yara.log"
|
||||||
],
|
],
|
||||||
"compression_gzip": false,
|
|
||||||
"use_logs_stream": false,
|
|
||||||
"data_stream.dataset": "soc",
|
"data_stream.dataset": "soc",
|
||||||
"pipeline": "common",
|
"pipeline": "common",
|
||||||
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
||||||
@@ -41,10 +35,10 @@
|
|||||||
"harvester_limit": 0,
|
"harvester_limit": 0,
|
||||||
"fingerprint": false,
|
"fingerprint": false,
|
||||||
"fingerprint_offset": 0,
|
"fingerprint_offset": 0,
|
||||||
|
"fingerprint_length": "64",
|
||||||
"file_identity_native": true,
|
"file_identity_native": true,
|
||||||
"exclude_lines": [],
|
"exclude_lines": [],
|
||||||
"include_lines": [],
|
"include_lines": []
|
||||||
"delete_enabled": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,25 +4,19 @@
|
|||||||
"version": ""
|
"version": ""
|
||||||
},
|
},
|
||||||
"name": "soc-salt-relay-logs",
|
"name": "soc-salt-relay-logs",
|
||||||
"namespace": "so",
|
|
||||||
"description": "Security Onion - Salt Relay - Logs",
|
"description": "Security Onion - Salt Relay - Logs",
|
||||||
"policy_id": "so-grid-nodes_general",
|
"policy_id": "so-grid-nodes_general",
|
||||||
"policy_ids": [
|
"namespace": "so",
|
||||||
"so-grid-nodes_general"
|
|
||||||
],
|
|
||||||
"vars": {},
|
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"filestream-filestream": {
|
"filestream-filestream": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"streams": {
|
"streams": {
|
||||||
"filestream.filestream": {
|
"filestream.generic": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"vars": {
|
"vars": {
|
||||||
"paths": [
|
"paths": [
|
||||||
"/opt/so/log/soc/salt-relay.log"
|
"/opt/so/log/soc/salt-relay.log"
|
||||||
],
|
],
|
||||||
"compression_gzip": false,
|
|
||||||
"use_logs_stream": false,
|
|
||||||
"data_stream.dataset": "soc",
|
"data_stream.dataset": "soc",
|
||||||
"pipeline": "common",
|
"pipeline": "common",
|
||||||
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
||||||
@@ -39,10 +33,10 @@
|
|||||||
"harvester_limit": 0,
|
"harvester_limit": 0,
|
||||||
"fingerprint": false,
|
"fingerprint": false,
|
||||||
"fingerprint_offset": 0,
|
"fingerprint_offset": 0,
|
||||||
|
"fingerprint_length": "64",
|
||||||
"file_identity_native": true,
|
"file_identity_native": true,
|
||||||
"exclude_lines": [],
|
"exclude_lines": [],
|
||||||
"include_lines": [],
|
"include_lines": []
|
||||||
"delete_enabled": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,25 +4,19 @@
|
|||||||
"version": ""
|
"version": ""
|
||||||
},
|
},
|
||||||
"name": "soc-sensoroni-logs",
|
"name": "soc-sensoroni-logs",
|
||||||
"namespace": "so",
|
|
||||||
"description": "Security Onion - Sensoroni - Logs",
|
"description": "Security Onion - Sensoroni - Logs",
|
||||||
"policy_id": "so-grid-nodes_general",
|
"policy_id": "so-grid-nodes_general",
|
||||||
"policy_ids": [
|
"namespace": "so",
|
||||||
"so-grid-nodes_general"
|
|
||||||
],
|
|
||||||
"vars": {},
|
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"filestream-filestream": {
|
"filestream-filestream": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"streams": {
|
"streams": {
|
||||||
"filestream.filestream": {
|
"filestream.generic": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"vars": {
|
"vars": {
|
||||||
"paths": [
|
"paths": [
|
||||||
"/opt/so/log/sensoroni/sensoroni.log"
|
"/opt/so/log/sensoroni/sensoroni.log"
|
||||||
],
|
],
|
||||||
"compression_gzip": false,
|
|
||||||
"use_logs_stream": false,
|
|
||||||
"data_stream.dataset": "soc",
|
"data_stream.dataset": "soc",
|
||||||
"pipeline": "common",
|
"pipeline": "common",
|
||||||
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
||||||
@@ -37,10 +31,10 @@
|
|||||||
"harvester_limit": 0,
|
"harvester_limit": 0,
|
||||||
"fingerprint": false,
|
"fingerprint": false,
|
||||||
"fingerprint_offset": 0,
|
"fingerprint_offset": 0,
|
||||||
|
"fingerprint_length": "64",
|
||||||
"file_identity_native": true,
|
"file_identity_native": true,
|
||||||
"exclude_lines": [],
|
"exclude_lines": [],
|
||||||
"include_lines": [],
|
"include_lines": []
|
||||||
"delete_enabled": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,25 +4,19 @@
|
|||||||
"version": ""
|
"version": ""
|
||||||
},
|
},
|
||||||
"name": "soc-server-logs",
|
"name": "soc-server-logs",
|
||||||
"namespace": "so",
|
|
||||||
"description": "Security Onion Console Logs",
|
"description": "Security Onion Console Logs",
|
||||||
"policy_id": "so-grid-nodes_general",
|
"policy_id": "so-grid-nodes_general",
|
||||||
"policy_ids": [
|
"namespace": "so",
|
||||||
"so-grid-nodes_general"
|
|
||||||
],
|
|
||||||
"vars": {},
|
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"filestream-filestream": {
|
"filestream-filestream": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"streams": {
|
"streams": {
|
||||||
"filestream.filestream": {
|
"filestream.generic": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"vars": {
|
"vars": {
|
||||||
"paths": [
|
"paths": [
|
||||||
"/opt/so/log/soc/sensoroni-server.log"
|
"/opt/so/log/soc/sensoroni-server.log"
|
||||||
],
|
],
|
||||||
"compression_gzip": false,
|
|
||||||
"use_logs_stream": false,
|
|
||||||
"data_stream.dataset": "soc",
|
"data_stream.dataset": "soc",
|
||||||
"pipeline": "common",
|
"pipeline": "common",
|
||||||
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
||||||
@@ -39,10 +33,10 @@
|
|||||||
"harvester_limit": 0,
|
"harvester_limit": 0,
|
||||||
"fingerprint": false,
|
"fingerprint": false,
|
||||||
"fingerprint_offset": 0,
|
"fingerprint_offset": 0,
|
||||||
|
"fingerprint_length": "64",
|
||||||
"file_identity_native": true,
|
"file_identity_native": true,
|
||||||
"exclude_lines": [],
|
"exclude_lines": [],
|
||||||
"include_lines": [],
|
"include_lines": []
|
||||||
"delete_enabled": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,25 +4,19 @@
|
|||||||
"version": ""
|
"version": ""
|
||||||
},
|
},
|
||||||
"name": "strelka-logs",
|
"name": "strelka-logs",
|
||||||
"namespace": "so",
|
|
||||||
"description": "Strelka Logs",
|
"description": "Strelka Logs",
|
||||||
"policy_id": "so-grid-nodes_general",
|
"policy_id": "so-grid-nodes_general",
|
||||||
"policy_ids": [
|
"namespace": "so",
|
||||||
"so-grid-nodes_general"
|
|
||||||
],
|
|
||||||
"vars": {},
|
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"filestream-filestream": {
|
"filestream-filestream": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"streams": {
|
"streams": {
|
||||||
"filestream.filestream": {
|
"filestream.generic": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"vars": {
|
"vars": {
|
||||||
"paths": [
|
"paths": [
|
||||||
"/nsm/strelka/log/strelka.log"
|
"/nsm/strelka/log/strelka.log"
|
||||||
],
|
],
|
||||||
"compression_gzip": false,
|
|
||||||
"use_logs_stream": false,
|
|
||||||
"data_stream.dataset": "strelka",
|
"data_stream.dataset": "strelka",
|
||||||
"pipeline": "strelka.file",
|
"pipeline": "strelka.file",
|
||||||
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
||||||
@@ -37,10 +31,10 @@
|
|||||||
"harvester_limit": 0,
|
"harvester_limit": 0,
|
||||||
"fingerprint": false,
|
"fingerprint": false,
|
||||||
"fingerprint_offset": 0,
|
"fingerprint_offset": 0,
|
||||||
|
"fingerprint_length": "64",
|
||||||
"file_identity_native": true,
|
"file_identity_native": true,
|
||||||
"exclude_lines": [],
|
"exclude_lines": [],
|
||||||
"include_lines": [],
|
"include_lines": []
|
||||||
"delete_enabled": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,25 +4,19 @@
|
|||||||
"version": ""
|
"version": ""
|
||||||
},
|
},
|
||||||
"name": "suricata-logs",
|
"name": "suricata-logs",
|
||||||
"namespace": "so",
|
|
||||||
"description": "Suricata integration",
|
"description": "Suricata integration",
|
||||||
"policy_id": "so-grid-nodes_general",
|
"policy_id": "so-grid-nodes_general",
|
||||||
"policy_ids": [
|
"namespace": "so",
|
||||||
"so-grid-nodes_general"
|
|
||||||
],
|
|
||||||
"vars": {},
|
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"filestream-filestream": {
|
"filestream-filestream": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"streams": {
|
"streams": {
|
||||||
"filestream.filestream": {
|
"filestream.generic": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"vars": {
|
"vars": {
|
||||||
"paths": [
|
"paths": [
|
||||||
"/nsm/suricata/eve*.json"
|
"/nsm/suricata/eve*.json"
|
||||||
],
|
],
|
||||||
"compression_gzip": false,
|
|
||||||
"use_logs_stream": false,
|
|
||||||
"data_stream.dataset": "suricata",
|
"data_stream.dataset": "suricata",
|
||||||
"pipeline": "suricata.common",
|
"pipeline": "suricata.common",
|
||||||
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
"parsers": "#- ndjson:\n# target: \"\"\n# message_key: msg\n#- multiline:\n# type: count\n# count_lines: 3\n",
|
||||||
@@ -37,10 +31,10 @@
|
|||||||
"harvester_limit": 0,
|
"harvester_limit": 0,
|
||||||
"fingerprint": false,
|
"fingerprint": false,
|
||||||
"fingerprint_offset": 0,
|
"fingerprint_offset": 0,
|
||||||
|
"fingerprint_length": "64",
|
||||||
"file_identity_native": true,
|
"file_identity_native": true,
|
||||||
"exclude_lines": [],
|
"exclude_lines": [],
|
||||||
"include_lines": [],
|
"include_lines": []
|
||||||
"delete_enabled": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
{# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
|
|
||||||
or more contributor license agreements. Licensed under the Elastic License 2.0; you may not use
|
|
||||||
this file except in compliance with the Elastic License 2.0. #}
|
|
||||||
|
|
||||||
|
|
||||||
{% import_json '/opt/so/state/esfleet_input_package_components.json' as ADDON_INPUT_PACKAGE_COMPONENTS %}
|
|
||||||
{% import_json '/opt/so/state/esfleet_component_templates.json' as INSTALLED_COMPONENT_TEMPLATES %}
|
|
||||||
{% import_yaml 'elasticfleet/defaults.yaml' as ELASTICFLEETDEFAULTS %}
|
|
||||||
|
|
||||||
{% set CORE_ESFLEET_PACKAGES = ELASTICFLEETDEFAULTS.get('elasticfleet', {}).get('packages', {}) %}
|
|
||||||
{% set ADDON_INPUT_INTEGRATION_DEFAULTS = {} %}
|
|
||||||
{% set DEBUG_STUFF = {} %}
|
|
||||||
|
|
||||||
{% for pkg in ADDON_INPUT_PACKAGE_COMPONENTS %}
|
|
||||||
{% if pkg.name in CORE_ESFLEET_PACKAGES %}
|
|
||||||
{# skip core input packages #}
|
|
||||||
{% elif pkg.name not in CORE_ESFLEET_PACKAGES %}
|
|
||||||
{# generate defaults for each input package #}
|
|
||||||
{% if pkg.dataStreams is defined and pkg.dataStreams is not none and pkg.dataStreams | length > 0 %}
|
|
||||||
{% for pattern in pkg.dataStreams %}
|
|
||||||
{# in ES 9.3.2 'input' type integrations no longer create default component templates and instead they wait for user input during 'integration' setup (fleet ui config)
|
|
||||||
title: generic is an artifact of that and is not in use #}
|
|
||||||
{% if pattern.title == "generic" %}
|
|
||||||
{% continue %}
|
|
||||||
{% endif %}
|
|
||||||
{% if "metrics-" in pattern.name %}
|
|
||||||
{% set integration_type = "metrics-" %}
|
|
||||||
{% elif "logs-" in pattern.name %}
|
|
||||||
{% set integration_type = "logs-" %}
|
|
||||||
{% else %}
|
|
||||||
{% set integration_type = "" %}
|
|
||||||
{% endif %}
|
|
||||||
{# on input integrations the component name is user defined at the time it is added to an agent policy #}
|
|
||||||
{% set component_name = pattern.title %}
|
|
||||||
{% set index_pattern = pattern.name %}
|
|
||||||
{# component_name_x maintains the functionality of merging local pillar changes with generated 'defaults' via SOC UI #}
|
|
||||||
{% set component_name_x = component_name.replace(".","_x_") %}
|
|
||||||
{# pillar overrides/merge expects the key names to follow the naming in elasticsearch/defaults.yaml eg. so-logs-1password_x_item_usages . The _x_ is replaced later on in elasticsearch/template.map.jinja #}
|
|
||||||
{% set integration_key = "so-" ~ integration_type ~ pkg.name + '_x_' ~ component_name_x %}
|
|
||||||
{# Default integration settings #}
|
|
||||||
{% set integration_defaults = {
|
|
||||||
"index_sorting": false,
|
|
||||||
"index_template": {
|
|
||||||
"composed_of": [integration_type ~ component_name ~ "@package", integration_type ~ component_name ~ "@custom", "so-fleet_integrations.ip_mappings-1", "so-fleet_globals-1", "so-fleet_agent_id_verification-1"],
|
|
||||||
"data_stream": {
|
|
||||||
"allow_custom_routing": false,
|
|
||||||
"hidden": false
|
|
||||||
},
|
|
||||||
"ignore_missing_component_templates": [integration_type ~ component_name ~ "@custom"],
|
|
||||||
"index_patterns": [index_pattern],
|
|
||||||
"priority": 501,
|
|
||||||
"template": {
|
|
||||||
"settings": {
|
|
||||||
"index": {
|
|
||||||
"lifecycle": {"name": "so-" ~ integration_type ~ component_name ~ "-logs"},
|
|
||||||
"number_of_replicas": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"policy": {
|
|
||||||
"phases": {
|
|
||||||
"cold": {
|
|
||||||
"actions": {
|
|
||||||
"allocate":{
|
|
||||||
"number_of_replicas": ""
|
|
||||||
},
|
|
||||||
"set_priority": {"priority": 0}
|
|
||||||
},
|
|
||||||
"min_age": "60d"
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
"actions": {
|
|
||||||
"delete": {}
|
|
||||||
},
|
|
||||||
"min_age": "365d"
|
|
||||||
},
|
|
||||||
"hot": {
|
|
||||||
"actions": {
|
|
||||||
"rollover": {
|
|
||||||
"max_age": "30d",
|
|
||||||
"max_primary_shard_size": "50gb"
|
|
||||||
},
|
|
||||||
"forcemerge":{
|
|
||||||
"max_num_segments": ""
|
|
||||||
},
|
|
||||||
"shrink":{
|
|
||||||
"max_primary_shard_size": "",
|
|
||||||
"method": "COUNT",
|
|
||||||
"number_of_shards": ""
|
|
||||||
},
|
|
||||||
"set_priority": {"priority": 100}
|
|
||||||
},
|
|
||||||
"min_age": "0ms"
|
|
||||||
},
|
|
||||||
"warm": {
|
|
||||||
"actions": {
|
|
||||||
"allocate": {
|
|
||||||
"number_of_replicas": ""
|
|
||||||
},
|
|
||||||
"forcemerge": {
|
|
||||||
"max_num_segments": ""
|
|
||||||
},
|
|
||||||
"shrink":{
|
|
||||||
"max_primary_shard_size": "",
|
|
||||||
"method": "COUNT",
|
|
||||||
"number_of_shards": ""
|
|
||||||
},
|
|
||||||
"set_priority": {"priority": 50}
|
|
||||||
},
|
|
||||||
"min_age": "30d"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} %}
|
|
||||||
|
|
||||||
|
|
||||||
{% do ADDON_INPUT_INTEGRATION_DEFAULTS.update({integration_key: integration_defaults}) %}
|
|
||||||
{% do DEBUG_STUFF.update({integration_key: "Generating defaults for "+ pkg.name })%}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
@@ -59,8 +59,8 @@
|
|||||||
{# skip core integrations #}
|
{# skip core integrations #}
|
||||||
{% elif pkg.name not in CORE_ESFLEET_PACKAGES %}
|
{% elif pkg.name not in CORE_ESFLEET_PACKAGES %}
|
||||||
{# generate defaults for each integration #}
|
{# generate defaults for each integration #}
|
||||||
{% if pkg.dataStreams is defined and pkg.dataStreams is not none and pkg.dataStreams | length > 0 %}
|
{% if pkg.es_index_patterns is defined and pkg.es_index_patterns is not none %}
|
||||||
{% for pattern in pkg.dataStreams %}
|
{% for pattern in pkg.es_index_patterns %}
|
||||||
{% if "metrics-" in pattern.name %}
|
{% if "metrics-" in pattern.name %}
|
||||||
{% set integration_type = "metrics-" %}
|
{% set integration_type = "metrics-" %}
|
||||||
{% elif "logs-" in pattern.name %}
|
{% elif "logs-" in pattern.name %}
|
||||||
@@ -75,27 +75,44 @@
|
|||||||
{% if component_name in WEIRD_INTEGRATIONS %}
|
{% if component_name in WEIRD_INTEGRATIONS %}
|
||||||
{% set component_name = WEIRD_INTEGRATIONS[component_name] %}
|
{% set component_name = WEIRD_INTEGRATIONS[component_name] %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{# create duplicate of component_name, so we can split generics from @custom component templates in the index template below and overwrite the default @package when needed
|
||||||
|
eg. having to replace unifiedlogs.generic@package with filestream.generic@package, but keep the ability to customize unifiedlogs.generic@custom and its ILM policy #}
|
||||||
|
{% set custom_component_name = component_name %}
|
||||||
|
|
||||||
|
{# duplicate integration_type to assist with sometimes needing to overwrite component templates with 'logs-filestream.generic@package' (there is no metrics-filestream.generic@package) #}
|
||||||
|
{% set generic_integration_type = integration_type %}
|
||||||
|
|
||||||
{# component_name_x maintains the functionality of merging local pillar changes with generated 'defaults' via SOC UI #}
|
{# component_name_x maintains the functionality of merging local pillar changes with generated 'defaults' via SOC UI #}
|
||||||
{% set component_name_x = component_name.replace(".","_x_") %}
|
{% set component_name_x = component_name.replace(".","_x_") %}
|
||||||
{# pillar overrides/merge expects the key names to follow the naming in elasticsearch/defaults.yaml eg. so-logs-1password_x_item_usages . The _x_ is replaced later on in elasticsearch/template.map.jinja #}
|
{# pillar overrides/merge expects the key names to follow the naming in elasticsearch/defaults.yaml eg. so-logs-1password_x_item_usages . The _x_ is replaced later on in elasticsearch/template.map.jinja #}
|
||||||
{% set integration_key = "so-" ~ integration_type ~ component_name_x %}
|
{% set integration_key = "so-" ~ integration_type ~ component_name_x %}
|
||||||
|
|
||||||
|
{# if its a .generic template make sure that a .generic@package for the integration exists. Else default to logs-filestream.generic@package #}
|
||||||
|
{% if ".generic" in component_name and integration_type ~ component_name ~ "@package" not in INSTALLED_COMPONENT_TEMPLATES %}
|
||||||
|
{# these generic templates by default are directed to index_pattern of 'logs-generic-*', overwrite that here to point to eg gcp_pubsub.generic-* #}
|
||||||
|
{% set index_pattern = integration_type ~ component_name ~ "-*" %}
|
||||||
|
{# includes use of .generic component template, but it doesn't exist in installed component templates. Redirect it to filestream.generic@package #}
|
||||||
|
{% set component_name = "filestream.generic" %}
|
||||||
|
{% set generic_integration_type = "logs-" %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{# Default integration settings #}
|
{# Default integration settings #}
|
||||||
{% set integration_defaults = {
|
{% set integration_defaults = {
|
||||||
"index_sorting": false,
|
"index_sorting": false,
|
||||||
"index_template": {
|
"index_template": {
|
||||||
"composed_of": [integration_type ~ component_name ~ "@package", integration_type ~ component_name ~ "@custom", "so-fleet_integrations.ip_mappings-1", "so-fleet_globals-1", "so-fleet_agent_id_verification-1"],
|
"composed_of": [generic_integration_type ~ component_name ~ "@package", integration_type ~ custom_component_name ~ "@custom", "so-fleet_integrations.ip_mappings-1", "so-fleet_globals-1", "so-fleet_agent_id_verification-1"],
|
||||||
"data_stream": {
|
"data_stream": {
|
||||||
"allow_custom_routing": false,
|
"allow_custom_routing": false,
|
||||||
"hidden": false
|
"hidden": false
|
||||||
},
|
},
|
||||||
"ignore_missing_component_templates": [integration_type ~ component_name ~ "@custom"],
|
"ignore_missing_component_templates": [integration_type ~ custom_component_name ~ "@custom"],
|
||||||
"index_patterns": [index_pattern],
|
"index_patterns": [index_pattern],
|
||||||
"priority": 501,
|
"priority": 501,
|
||||||
"template": {
|
"template": {
|
||||||
"settings": {
|
"settings": {
|
||||||
"index": {
|
"index": {
|
||||||
"lifecycle": {"name": "so-" ~ integration_type ~ component_name ~ "-logs"},
|
"lifecycle": {"name": "so-" ~ integration_type ~ custom_component_name ~ "-logs"},
|
||||||
"number_of_replicas": 0
|
"number_of_replicas": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
|
|
||||||
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
|
|
||||||
# https://securityonion.net/license; you may not use this file except in compliance with the
|
|
||||||
# Elastic License 2.0.
|
|
||||||
|
|
||||||
{% from 'allowed_states.map.jinja' import allowed_states %}
|
|
||||||
{% if sls in allowed_states %}
|
|
||||||
{% from 'elasticfleet/map.jinja' import ELASTICFLEETMERGED %}
|
|
||||||
|
|
||||||
include:
|
|
||||||
- elasticfleet.config
|
|
||||||
|
|
||||||
# If enabled, automatically update Fleet Logstash Outputs
|
|
||||||
{% if ELASTICFLEETMERGED.config.server.enable_auto_configuration %}
|
|
||||||
{% if grains.role not in ['so-import', 'so-eval']%}
|
|
||||||
so-elastic-fleet-auto-configure-logstash-outputs:
|
|
||||||
cmd.run:
|
|
||||||
- name: /usr/sbin/so-elastic-fleet-outputs-update
|
|
||||||
- retry:
|
|
||||||
attempts: 4
|
|
||||||
interval: 30
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
# If enabled, automatically update Fleet Server URLs & ES Connection
|
|
||||||
so-elastic-fleet-auto-configure-server-urls:
|
|
||||||
cmd.run:
|
|
||||||
- name: /usr/sbin/so-elastic-fleet-urls-update
|
|
||||||
- retry:
|
|
||||||
attempts: 4
|
|
||||||
interval: 30
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
# Automatically update Fleet Server Elasticsearch URLs & Agent Artifact URLs
|
|
||||||
so-elastic-fleet-auto-configure-elasticsearch-urls:
|
|
||||||
cmd.run:
|
|
||||||
- name: /usr/sbin/so-elastic-fleet-es-url-update
|
|
||||||
- retry:
|
|
||||||
attempts: 4
|
|
||||||
interval: 30
|
|
||||||
|
|
||||||
so-elastic-fleet-auto-configure-artifact-urls:
|
|
||||||
cmd.run:
|
|
||||||
- name: /usr/sbin/so-elastic-fleet-artifacts-url-update
|
|
||||||
- retry:
|
|
||||||
attempts: 4
|
|
||||||
interval: 30
|
|
||||||
|
|
||||||
so-elastic-fleet-package-statefile:
|
|
||||||
file.managed:
|
|
||||||
- name: /opt/so/state/elastic_fleet_packages.txt
|
|
||||||
- contents: {{ELASTICFLEETMERGED.packages}}
|
|
||||||
|
|
||||||
so-elastic-fleet-package-upgrade:
|
|
||||||
cmd.run:
|
|
||||||
- name: /usr/sbin/so-elastic-fleet-package-upgrade
|
|
||||||
- retry:
|
|
||||||
attempts: 3
|
|
||||||
interval: 10
|
|
||||||
- onchanges:
|
|
||||||
- file: /opt/so/state/elastic_fleet_packages.txt
|
|
||||||
|
|
||||||
so-elastic-fleet-integrations:
|
|
||||||
cmd.run:
|
|
||||||
- name: /usr/sbin/so-elastic-fleet-integration-policy-load
|
|
||||||
- retry:
|
|
||||||
attempts: 3
|
|
||||||
interval: 10
|
|
||||||
|
|
||||||
so-elastic-agent-grid-upgrade:
|
|
||||||
cmd.run:
|
|
||||||
- name: /usr/sbin/so-elastic-agent-grid-upgrade
|
|
||||||
- retry:
|
|
||||||
attempts: 12
|
|
||||||
interval: 5
|
|
||||||
|
|
||||||
so-elastic-fleet-integration-upgrade:
|
|
||||||
cmd.run:
|
|
||||||
- name: /usr/sbin/so-elastic-fleet-integration-upgrade
|
|
||||||
- retry:
|
|
||||||
attempts: 3
|
|
||||||
interval: 10
|
|
||||||
|
|
||||||
{# Optional integrations script doesn't need the retries like so-elastic-fleet-integration-upgrade which loads the default integrations #}
|
|
||||||
so-elastic-fleet-addon-integrations:
|
|
||||||
cmd.run:
|
|
||||||
- name: /usr/sbin/so-elastic-fleet-optional-integrations-load
|
|
||||||
|
|
||||||
{% if ELASTICFLEETMERGED.config.defend_filters.enable_auto_configuration %}
|
|
||||||
so-elastic-defend-manage-filters-file-watch:
|
|
||||||
cmd.run:
|
|
||||||
- name: python3 /sbin/so-elastic-defend-manage-filters.py -c /opt/so/conf/elasticsearch/curl.config -d /opt/so/conf/elastic-fleet/defend-exclusions/disabled-filters.yaml -i /nsm/securityonion-resources/event_filters/ -i /opt/so/conf/elastic-fleet/defend-exclusions/rulesets/custom-filters/ &>> /opt/so/log/elasticfleet/elastic-defend-manage-filters.log
|
|
||||||
- onchanges:
|
|
||||||
- file: elasticdefendcustom
|
|
||||||
- file: elasticdefenddisabled
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
{{sls}}_state_not_allowed:
|
|
||||||
test.fail_without_changes:
|
|
||||||
- name: {{sls}}_state_not_allowed
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
@@ -135,33 +135,9 @@ elastic_fleet_bulk_package_install() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
elastic_fleet_get_package_list_by_type() {
|
elastic_fleet_installed_packages() {
|
||||||
if ! output=$(fleet_api "epm/packages"); then
|
if ! fleet_api "epm/packages/installed?perPage=500"; then
|
||||||
return 1
|
return 1
|
||||||
else
|
|
||||||
is_integration=$(jq '[.items[] | select(.type=="integration") | .name ]' <<< "$output")
|
|
||||||
is_input=$(jq '[.items[] | select(.type=="input") | .name ]' <<< "$output")
|
|
||||||
is_content=$(jq '[.items[] | select(.type=="content") | .name ]' <<< "$output")
|
|
||||||
jq -n --argjson is_integration "${is_integration:-[]}" \
|
|
||||||
--argjson is_input "${is_input:-[]}" \
|
|
||||||
--argjson is_content "${is_content:-[]}" \
|
|
||||||
'{"integration": $is_integration,"input": $is_input, "content": $is_content}'
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
elastic_fleet_installed_packages_components() {
|
|
||||||
package_type=${1,,}
|
|
||||||
if [[ "$package_type" != "integration" && "$package_type" != "input" && "$package_type" != "content" ]]; then
|
|
||||||
echo "Error: Invalid package type ${package_type}. Valid types are 'integration', 'input', or 'content'."
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
packages_by_type=$(elastic_fleet_get_package_list_by_type)
|
|
||||||
packages=$(jq --arg package_type "$package_type" '.[$package_type]' <<< "$packages_by_type")
|
|
||||||
|
|
||||||
if ! output=$(fleet_api "epm/packages/installed?perPage=500"); then
|
|
||||||
return 1
|
|
||||||
else
|
|
||||||
jq -c --argjson packages "$packages" '[.items[] | select(.name | IN($packages[])) | {name: .name, dataStreams: .dataStreams}]' <<< "$output"
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,7 +216,7 @@ elastic_fleet_policy_create() {
|
|||||||
--arg DESC "$DESC" \
|
--arg DESC "$DESC" \
|
||||||
--arg TIMEOUT $TIMEOUT \
|
--arg TIMEOUT $TIMEOUT \
|
||||||
--arg FLEETSERVER "$FLEETSERVER" \
|
--arg FLEETSERVER "$FLEETSERVER" \
|
||||||
'{"name": $NAME,"id":$NAME,"description":$DESC,"namespace":"default","monitoring_enabled":["logs"],"inactivity_timeout":$TIMEOUT,"has_fleet_server":$FLEETSERVER,"advanced_settings":{"agent_logging_level": "warning"}}'
|
'{"name": $NAME,"id":$NAME,"description":$DESC,"namespace":"default","monitoring_enabled":["logs"],"inactivity_timeout":$TIMEOUT,"has_fleet_server":$FLEETSERVER}'
|
||||||
)
|
)
|
||||||
# Create Fleet Policy
|
# Create Fleet Policy
|
||||||
if ! fleet_api "agent_policies" -XPOST -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"; then
|
if ! fleet_api "agent_policies" -XPOST -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"; then
|
||||||
|
|||||||
@@ -5,13 +5,7 @@
|
|||||||
# this file except in compliance with the Elastic License 2.0.
|
# this file except in compliance with the Elastic License 2.0.
|
||||||
|
|
||||||
. /usr/sbin/so-common
|
. /usr/sbin/so-common
|
||||||
. /usr/sbin/so-elastic-fleet-common
|
|
||||||
{%- import_yaml 'elasticsearch/defaults.yaml' as ELASTICSEARCHDEFAULTS %}
|
{%- import_yaml 'elasticsearch/defaults.yaml' as ELASTICSEARCHDEFAULTS %}
|
||||||
{%- import_yaml 'elasticfleet/defaults.yaml' as ELASTICFLEETDEFAULTS %}
|
|
||||||
{# Optionally override Elasticsearch version for Elastic Agent patch releases #}
|
|
||||||
{%- if ELASTICFLEETDEFAULTS.elasticfleet.patch_version is defined %}
|
|
||||||
{%- do ELASTICSEARCHDEFAULTS.elasticsearch.update({'version': ELASTICFLEETDEFAULTS.elasticfleet.patch_version}) %}
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
# Only run on Managers
|
# Only run on Managers
|
||||||
if ! is_manager_node; then
|
if ! is_manager_node; then
|
||||||
@@ -20,10 +14,13 @@ if ! is_manager_node; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Get current list of Grid Node Agents that need to be upgraded
|
# Get current list of Grid Node Agents that need to be upgraded
|
||||||
if ! RAW_JSON=$(fleet_api "agents?perPage=20&page=1&kuery=NOT%20agent.version%3A%20{{ELASTICSEARCHDEFAULTS.elasticsearch.version | urlencode }}%20AND%20policy_id%3A%20so-grid-nodes_%2A&showInactive=false&getStatusSummary=true" -H 'kbn-xsrf: true' -H 'Content-Type: application/json'); then
|
RAW_JSON=$(curl -K /opt/so/conf/elasticsearch/curl.config -L "http://localhost:5601/api/fleet/agents?perPage=20&page=1&kuery=NOT%20agent.version%3A%20{{ELASTICSEARCHDEFAULTS.elasticsearch.version}}%20AND%20policy_id%3A%20so-grid-nodes_%2A&showInactive=false&getStatusSummary=true" --retry 3 --retry-delay 30 --fail 2>/dev/null)
|
||||||
|
|
||||||
printf "Failed to query for current Grid Agents...\n"
|
# Check to make sure that the server responded with good data - else, bail from script
|
||||||
exit 1
|
CHECKSUM=$(jq -r '.page' <<< "$RAW_JSON")
|
||||||
|
if [ "$CHECKSUM" -ne 1 ]; then
|
||||||
|
printf "Failed to query for current Grid Agents...\n"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Generate list of Node Agents that need updates
|
# Generate list of Node Agents that need updates
|
||||||
@@ -34,12 +31,10 @@ if [ "$OUTDATED_LIST" != '[]' ]; then
|
|||||||
printf "Initiating upgrades for $AGENTNUMBERS Agents to Elastic {{ELASTICSEARCHDEFAULTS.elasticsearch.version}}...\n\n"
|
printf "Initiating upgrades for $AGENTNUMBERS Agents to Elastic {{ELASTICSEARCHDEFAULTS.elasticsearch.version}}...\n\n"
|
||||||
|
|
||||||
# Generate updated JSON payload
|
# Generate updated JSON payload
|
||||||
JSON_STRING=$(jq -n --arg ELASTICVERSION "{{ELASTICSEARCHDEFAULTS.elasticsearch.version}}" --argjson UPDATELIST "$OUTDATED_LIST" '{"version": $ELASTICVERSION,"agents": $UPDATELIST }')
|
JSON_STRING=$(jq -n --arg ELASTICVERSION {{ELASTICSEARCHDEFAULTS.elasticsearch.version}} --arg UPDATELIST $OUTDATED_LIST '{"version": $ELASTICVERSION,"agents": $UPDATELIST }')
|
||||||
|
|
||||||
# Update Node Agents
|
# Update Node Agents
|
||||||
if ! fleet_api "agents/bulk_upgrade" -XPOST -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"; then
|
curl -K /opt/so/conf/elasticsearch/curl.config -L -X POST "http://localhost:5601/api/fleet/agents/bulk_upgrade" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"
|
||||||
printf "Failed to initiate Agent upgrades...\n"
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
printf "No Agents need updates... Exiting\n\n"
|
printf "No Agents need updates... Exiting\n\n"
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -16,10 +16,9 @@
|
|||||||
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
|
PACKAGE_COMPONENTS=/opt/so/state/esfleet_package_components.json
|
||||||
INPUT_PACKAGE_COMPONENTS=/opt/so/state/esfleet_input_package_components.json
|
|
||||||
CONTENT_PACKAGE_COMPONENTS=/opt/so/state/esfleet_content_package_components.json
|
|
||||||
COMPONENT_TEMPLATES=/opt/so/state/esfleet_component_templates.json
|
COMPONENT_TEMPLATES=/opt/so/state/esfleet_component_templates.json
|
||||||
|
|
||||||
PENDING_UPDATE=false
|
PENDING_UPDATE=false
|
||||||
@@ -28,6 +27,29 @@ 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
|
||||||
@@ -39,23 +61,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 ! policy_json=$(fleet_api "agent_policies/$AGENT_POLICY"); then
|
if ! integrations=$(elastic_fleet_integration_policy_names "$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
|
||||||
# non-default integrations that are in-use in any policy
|
for INTEGRATION in $integrations; do
|
||||||
while IFS= read -r PACKAGE_NAME; do
|
if ! PACKAGE_NAME=$(elastic_fleet_integration_policy_package_name "$AGENT_POLICY" "$INTEGRATION"); then
|
||||||
[ -n "$PACKAGE_NAME" ] && in_use_integrations+=("$PACKAGE_NAME")
|
echo "Not adding $INTEGRATION, couldn't get package name"
|
||||||
done < <(jq -r --argjson defaults "$default_packages_json" \
|
continue
|
||||||
'.item.package_policies[].package.name | select(. as $n | ($defaults | index($n)) | not)' \
|
fi
|
||||||
<<<"$policy_json")
|
# non-default integrations that are in-use in any policy
|
||||||
|
if ! [[ " ${default_packages[@]} " =~ " $PACKAGE_NAME " ]]; then
|
||||||
|
in_use_integrations+=("$PACKAGE_NAME")
|
||||||
|
fi
|
||||||
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -f $STATE_FILE_SUCCESS ]]; then
|
if [[ -f $STATE_FILE_SUCCESS ]]; then
|
||||||
@@ -66,55 +88,72 @@ 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
|
||||||
|
|
||||||
# Build the bulk install list and the per-package status messages with two jq passes
|
while read -r package; do
|
||||||
# instead of a per-package bash loop. The old loop forked ~10 processes per package
|
# get package details
|
||||||
# (5 jq + awk/bc for the version compare) and re-parsed/rewrote a growing JSON file on
|
package_name=$(echo "$package" | jq -r '.name')
|
||||||
# every add (O(n^2)). Selection and messages below are identical to that logic.
|
latest_version=$(echo "$package" | jq -r '.latest_version')
|
||||||
SUB={% if SUB %}true{% else %}false{% endif %}
|
installed_version=$(echo "$package" | jq -r '.installed_version')
|
||||||
AUTOUP={% if AUTO_UPGRADE_INTEGRATIONS %}true{% else %}false{% endif %}
|
subscription=$(echo "$package" | jq -r '.subscription')
|
||||||
EXCLUDED_JSON=$(printf '%s\n' "${EXCLUDED_INTEGRATIONS[@]}" | jq -R 'select(length>0)' | jq -s '.')
|
bulk_package=$(echo "$package" | jq '{name: .name, version: .latest_version}' )
|
||||||
INUSE_JSON=$(printf '%s\n' "${in_use_integrations[@]}" | jq -R 'select(length>0)' | jq -s 'unique')
|
|
||||||
|
|
||||||
# vnum replicates the previous version_conversion (%d%03d%03d of the first three dotted
|
if [[ ! "${EXCLUDED_INTEGRATIONS[@]}" =~ "$package_name" ]]; then
|
||||||
# fields); needs() replicates the excluded/subscription/installed/upgrade/in-use logic.
|
{% if not SUB %}
|
||||||
JQ_DECISION='
|
if [[ "$subscription" != "basic" && "$subscription" != "null" && -n "$subscription" ]]; then
|
||||||
def vnum:
|
# pass over integrations that require non-basic elastic license
|
||||||
[ (split(".")|.[0:3][] | gsub("[^0-9].*";"") | (if .=="" then "0" else . end) | tonumber) ]
|
echo "$package_name integration requires an Elastic license of $subscription or greater... skipping"
|
||||||
| (.[0]//0)*1000000 + (.[1]//0)*1000 + (.[2]//0);
|
continue
|
||||||
def needs($sub;$autoup;$excluded;$inuse):
|
else
|
||||||
.name as $n
|
if [[ "$installed_version" == "null" || -z "$installed_version" ]]; then
|
||||||
| ($n | IN($excluded[]) | not)
|
echo "$package_name is not installed... Adding to next update."
|
||||||
and ( $sub or (.subscription==null or .subscription=="basic" or .subscription=="") )
|
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 ( (.installed_version==null or .installed_version=="")
|
|
||||||
or ( ((.latest_version|vnum) > (.installed_version|vnum))
|
|
||||||
and ( $autoup or ($n | IN($inuse[]) | not) ) ) );'
|
|
||||||
|
|
||||||
JQ_ARGS=(--argjson sub "$SUB" --argjson autoup "$AUTOUP" --argjson excluded "$EXCLUDED_JSON" --argjson inuse "$INUSE_JSON")
|
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
|
||||||
|
|
||||||
# (a) Per-package status messages (parity with the previous echo output).
|
PENDING_UPDATE=true
|
||||||
jq -r "${JQ_ARGS[@]}" "$JQ_DECISION"'
|
{%- if not AUTO_UPGRADE_INTEGRATIONS %}
|
||||||
.packages[]
|
else
|
||||||
| .name as $n
|
echo "skipping available upgrade for in use integration - $package_name."
|
||||||
| if ($n|IN($excluded[])) then "Skipping \($n)..."
|
fi
|
||||||
elif (($sub|not) and (.subscription!=null and .subscription!="basic" and .subscription!="")) then
|
{%- endif %}
|
||||||
"\($n) integration requires an Elastic license of \(.subscription) or greater... skipping"
|
fi
|
||||||
elif (.installed_version==null or .installed_version=="") then
|
fi
|
||||||
"\($n) is not installed... Adding to next update."
|
fi
|
||||||
elif ((.latest_version|vnum) > (.installed_version|vnum)) then
|
{% else %}
|
||||||
(if ($autoup or ($n|IN($inuse[])|not))
|
if [[ "$installed_version" == "null" || -z "$installed_version" ]]; then
|
||||||
then "\($n) is at version \(.installed_version) latest version is \(.latest_version)... Adding to next update."
|
echo "$package_name is not installed... Adding to next update."
|
||||||
else "skipping available upgrade for in use integration - \($n)." end)
|
jq --argjson package "$bulk_package" '.packages += [$package]' $BULK_INSTALL_PACKAGE_LIST > $BULK_INSTALL_PACKAGE_TMP && mv $BULK_INSTALL_PACKAGE_TMP $BULK_INSTALL_PACKAGE_LIST
|
||||||
else empty end
|
PENDING_UPDATE=true
|
||||||
' "$INSTALLED_PACKAGE_LIST"
|
else
|
||||||
|
results=$(compare_versions "$latest_version" "$installed_version")
|
||||||
# (b) The bulk install list, built in a single pass.
|
if [ $results == "greater" ]; then
|
||||||
jq "${JQ_ARGS[@]}" "$JQ_DECISION"'
|
{#- When auto_upgrade_integrations is false, skip upgrading in_use_integrations #}
|
||||||
{packages: [ .packages[] | select(needs($sub;$autoup;$excluded;$inuse)) | {name, version: .latest_version} ]}
|
{%- if not AUTO_UPGRADE_INTEGRATIONS %}
|
||||||
' "$INSTALLED_PACKAGE_LIST" > "$BULK_INSTALL_PACKAGE_LIST"
|
if ! [[ " ${in_use_integrations[@]} " =~ " $package_name " ]]; then
|
||||||
|
{%- endif %}
|
||||||
if jq -e '.packages | length > 0' "$BULK_INSTALL_PACKAGE_LIST" >/dev/null; then
|
echo "$package_name is at version $installed_version latest version is $latest_version... Adding to next update."
|
||||||
PENDING_UPDATE=true
|
jq --argjson package "$bulk_package" '.packages += [$package]' $BULK_INSTALL_PACKAGE_LIST > $BULK_INSTALL_PACKAGE_TMP && mv $BULK_INSTALL_PACKAGE_TMP $BULK_INSTALL_PACKAGE_LIST
|
||||||
fi
|
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
|
||||||
@@ -140,13 +179,10 @@ def needs($sub;$autoup;$excluded;$inuse):
|
|||||||
else
|
else
|
||||||
echo "Elastic integrations don't appear to need installation/updating..."
|
echo "Elastic integrations don't appear to need installation/updating..."
|
||||||
fi
|
fi
|
||||||
# Write out file for generating index/component/ilm templates, keeping each package type separate
|
# Write out file for generating index/component/ilm templates
|
||||||
for package_type in "INTEGRATION" "INPUT" "CONTENT"; do
|
if latest_installed_package_list=$(elastic_fleet_installed_packages); then
|
||||||
if latest_installed_package_list=$(elastic_fleet_installed_packages_components "$package_type"); then
|
echo $latest_installed_package_list | jq '[.items[] | {name: .name, es_index_patterns: .dataStreams}]' > $PACKAGE_COMPONENTS
|
||||||
outfile="${package_type}_PACKAGE_COMPONENTS"
|
fi
|
||||||
echo $latest_installed_package_list > "${!outfile}"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if retry 3 1 "so-elasticsearch-query / --fail --output /dev/null"; then
|
if retry 3 1 "so-elasticsearch-query / --fail --output /dev/null"; then
|
||||||
# Refresh installed component template list
|
# Refresh installed component template list
|
||||||
latest_component_templates_list=$(so-elasticsearch-query _component_template | jq '.component_templates[] | .name' | jq -s '.')
|
latest_component_templates_list=$(so-elasticsearch-query _component_template | jq '.component_templates[] | .name' | jq -s '.')
|
||||||
|
|||||||
@@ -235,16 +235,6 @@ function update_kafka_outputs() {
|
|||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
# Compare the current Elastic Fleet certificate against what is on disk
|
|
||||||
POLICY_CERT_SHA=$(jq -r '.item.ssl.certificate' <<< $RAW_JSON | openssl x509 -noout -sha256 -fingerprint)
|
|
||||||
DISK_CERT_SHA=$(openssl x509 -in /etc/pki/elasticfleet-logstash.crt -noout -sha256 -fingerprint)
|
|
||||||
|
|
||||||
if [[ "$POLICY_CERT_SHA" != "$DISK_CERT_SHA" ]]; then
|
|
||||||
printf "Certificate on disk doesn't match certificate in policy - forcing update\n"
|
|
||||||
UPDATE_CERTS=true
|
|
||||||
FORCE_UPDATE=true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Sort & hash the new list of Logstash Outputs
|
# Sort & hash the new list of Logstash Outputs
|
||||||
NEW_LIST_JSON=$(jq --compact-output --null-input '$ARGS.positional' --args -- "${NEW_LIST[@]}")
|
NEW_LIST_JSON=$(jq --compact-output --null-input '$ARGS.positional' --args -- "${NEW_LIST[@]}")
|
||||||
NEW_HASH=$(sha256sum <<< "$NEW_LIST_JSON" | awk '{print $1}')
|
NEW_HASH=$(sha256sum <<< "$NEW_LIST_JSON" | awk '{print $1}')
|
||||||
|
|||||||
@@ -1,181 +0,0 @@
|
|||||||
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
|
|
||||||
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
|
|
||||||
# https://securityonion.net/license; you may not use this file except in compliance with the
|
|
||||||
# Elastic License 2.0.
|
|
||||||
|
|
||||||
{% from 'allowed_states.map.jinja' import allowed_states %}
|
|
||||||
{% if sls in allowed_states %}
|
|
||||||
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
|
||||||
{% from 'elasticsearch/config.map.jinja' import ELASTICSEARCHMERGED %}
|
|
||||||
{% from 'elasticsearch/template.map.jinja' import ES_INDEX_SETTINGS, SO_MANAGED_INDICES %}
|
|
||||||
{% if GLOBALS.role != 'so-heavynode' %}
|
|
||||||
{% from 'elasticsearch/template.map.jinja' import ALL_ADDON_SETTINGS, ADDON_INDICES %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
include:
|
|
||||||
- elasticsearch.enabled
|
|
||||||
|
|
||||||
escomponenttemplates:
|
|
||||||
file.recurse:
|
|
||||||
- name: /opt/so/conf/elasticsearch/templates/component
|
|
||||||
- source: salt://elasticsearch/templates/component
|
|
||||||
- user: 930
|
|
||||||
- group: 939
|
|
||||||
- clean: True
|
|
||||||
- onchanges_in:
|
|
||||||
- file: so-elasticsearch-templates-reload
|
|
||||||
- show_changes: False
|
|
||||||
|
|
||||||
# Clean up legacy and non-SO managed templates from the elasticsearch/templates/index/ directory
|
|
||||||
so_index_template_dir:
|
|
||||||
file.directory:
|
|
||||||
- name: /opt/so/conf/elasticsearch/templates/index
|
|
||||||
- clean: True
|
|
||||||
{%- if SO_MANAGED_INDICES %}
|
|
||||||
- require:
|
|
||||||
{%- for index in SO_MANAGED_INDICES %}
|
|
||||||
- file: so_index_template_{{index}}
|
|
||||||
{%- endfor %}
|
|
||||||
{%- 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)
|
|
||||||
# These index templates are for the core SO datasets and are always required
|
|
||||||
{% for index, settings in ES_INDEX_SETTINGS.items() %}
|
|
||||||
{% if settings.index_template is defined %}
|
|
||||||
so_index_template_{{index}}:
|
|
||||||
file.managed:
|
|
||||||
- name: /opt/so/conf/elasticsearch/templates/index/{{ index }}-template.json
|
|
||||||
- source: salt://elasticsearch/base-template.json.jinja
|
|
||||||
- defaults:
|
|
||||||
TEMPLATE_CONFIG: {{ settings.index_template }}
|
|
||||||
- template: jinja
|
|
||||||
- onchanges_in:
|
|
||||||
- file: so-elasticsearch-templates-reload
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% if GLOBALS.role != "so-heavynode" %}
|
|
||||||
# Auto-generate optional index templates for integration | input | content packages
|
|
||||||
# These index templates are not used by default (until user adds package to an agent policy).
|
|
||||||
# Pre-configured with standard defaults, and incorporated into SOC configuration for user customization.
|
|
||||||
{% for index,settings in ALL_ADDON_SETTINGS.items() %}
|
|
||||||
{% if settings.index_template is defined %}
|
|
||||||
addon_index_template_{{index}}:
|
|
||||||
file.managed:
|
|
||||||
- name: /opt/so/conf/elasticsearch/templates/addon-index/{{ index }}-template.json
|
|
||||||
- source: salt://elasticsearch/base-template.json.jinja
|
|
||||||
- defaults:
|
|
||||||
TEMPLATE_CONFIG: {{ settings.index_template }}
|
|
||||||
- template: jinja
|
|
||||||
- show_changes: False
|
|
||||||
- onchanges_in:
|
|
||||||
- file: addon-elasticsearch-templates-reload
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if GLOBALS.role in GLOBALS.manager_roles %}
|
|
||||||
so-es-cluster-settings:
|
|
||||||
cmd.run:
|
|
||||||
- name: /usr/sbin/so-elasticsearch-cluster-settings
|
|
||||||
- cwd: /opt/so
|
|
||||||
- template: jinja
|
|
||||||
- require:
|
|
||||||
- docker_container: so-elasticsearch
|
|
||||||
- file: elasticsearch_sbin_jinja
|
|
||||||
- http: wait_for_so-elasticsearch
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
# heavynodes will only load ILM policies for SO managed indices. (Indicies defined in elasticsearch/defaults.yaml)
|
|
||||||
so-elasticsearch-ilm-policy-load:
|
|
||||||
cmd.run:
|
|
||||||
- name: /usr/sbin/so-elasticsearch-ilm-policy-load
|
|
||||||
- cwd: /opt/so
|
|
||||||
- require:
|
|
||||||
- docker_container: so-elasticsearch
|
|
||||||
- file: so-elasticsearch-ilm-policy-load-script
|
|
||||||
- onchanges:
|
|
||||||
- file: so-elasticsearch-ilm-policy-load-script
|
|
||||||
|
|
||||||
so-elasticsearch-templates-reload:
|
|
||||||
file.absent:
|
|
||||||
- name: /opt/so/state/estemplates.txt
|
|
||||||
|
|
||||||
addon-elasticsearch-templates-reload:
|
|
||||||
file.absent:
|
|
||||||
- name: /opt/so/state/addon_estemplates.txt
|
|
||||||
|
|
||||||
# so-elasticsearch-templates-load will have its first successful run during the 'so-elastic-fleet-setup' script
|
|
||||||
so-elasticsearch-templates:
|
|
||||||
cmd.run:
|
|
||||||
{%- if GLOBALS.role == "so-heavynode" %}
|
|
||||||
- name: /usr/sbin/so-elasticsearch-templates-load --heavynode
|
|
||||||
{%- else %}
|
|
||||||
- name: /usr/sbin/so-elasticsearch-templates-load
|
|
||||||
{%- endif %}
|
|
||||||
- cwd: /opt/so
|
|
||||||
- template: jinja
|
|
||||||
- require:
|
|
||||||
- docker_container: so-elasticsearch
|
|
||||||
- file: elasticsearch_sbin_jinja
|
|
||||||
|
|
||||||
so-elasticsearch-pipelines:
|
|
||||||
cmd.run:
|
|
||||||
- name: /usr/sbin/so-elasticsearch-pipelines {{ GLOBALS.hostname }}
|
|
||||||
- require:
|
|
||||||
- docker_container: so-elasticsearch
|
|
||||||
- file: so-elasticsearch-pipelines-script
|
|
||||||
|
|
||||||
so-elasticsearch-roles-load:
|
|
||||||
cmd.run:
|
|
||||||
- name: /usr/sbin/so-elasticsearch-roles-load
|
|
||||||
- cwd: /opt/so
|
|
||||||
- template: jinja
|
|
||||||
- require:
|
|
||||||
- docker_container: so-elasticsearch
|
|
||||||
- file: elasticsearch_sbin_jinja
|
|
||||||
|
|
||||||
{% if grains.role in ['so-managersearch', 'so-manager', 'so-managerhype'] %}
|
|
||||||
{% set ap = "absent" %}
|
|
||||||
{% endif %}
|
|
||||||
{% if grains.role in ['so-eval', 'so-standalone', 'so-heavynode'] %}
|
|
||||||
{% if ELASTICSEARCHMERGED.index_clean %}
|
|
||||||
{% set ap = "present" %}
|
|
||||||
{% else %}
|
|
||||||
{% set ap = "absent" %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% if grains.role in ['so-eval', 'so-standalone', 'so-managersearch', 'so-heavynode', 'so-manager'] %}
|
|
||||||
so-elasticsearch-indices-delete:
|
|
||||||
cron.{{ap}}:
|
|
||||||
- name: /usr/sbin/so-elasticsearch-indices-delete > /opt/so/log/elasticsearch/cron-elasticsearch-indices-delete.log 2>&1
|
|
||||||
- identifier: so-elasticsearch-indices-delete
|
|
||||||
- user: root
|
|
||||||
- minute: '*/5'
|
|
||||||
- hour: '*'
|
|
||||||
- daymonth: '*'
|
|
||||||
- month: '*'
|
|
||||||
- dayweek: '*'
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
{{sls}}_state_not_allowed:
|
|
||||||
test.fail_without_changes:
|
|
||||||
- name: {{sls}}_state_not_allowed
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
@@ -66,8 +66,6 @@ so-elasticsearch-ilm-policy-load-script:
|
|||||||
- group: 939
|
- group: 939
|
||||||
- mode: 754
|
- mode: 754
|
||||||
- template: jinja
|
- template: jinja
|
||||||
- defaults:
|
|
||||||
GLOBALS: {{ GLOBALS }}
|
|
||||||
- show_changes: False
|
- show_changes: False
|
||||||
|
|
||||||
so-elasticsearch-pipelines-script:
|
so-elasticsearch-pipelines-script:
|
||||||
@@ -93,13 +91,6 @@ estemplatedir:
|
|||||||
- group: 939
|
- group: 939
|
||||||
- makedirs: True
|
- makedirs: True
|
||||||
|
|
||||||
esaddontemplatedir:
|
|
||||||
file.directory:
|
|
||||||
- name: /opt/so/conf/elasticsearch/templates/addon-index
|
|
||||||
- user: 930
|
|
||||||
- group: 939
|
|
||||||
- makedirs: True
|
|
||||||
|
|
||||||
esrolesdir:
|
esrolesdir:
|
||||||
file.directory:
|
file.directory:
|
||||||
- name: /opt/so/conf/elasticsearch/roles
|
- name: /opt/so/conf/elasticsearch/roles
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
elasticsearch:
|
elasticsearch:
|
||||||
enabled: false
|
enabled: false
|
||||||
version: 9.3.3
|
version: 9.0.8
|
||||||
index_clean: true
|
index_clean: true
|
||||||
vm:
|
vm:
|
||||||
max_map_count: 1048576
|
max_map_count: 1048576
|
||||||
@@ -3958,13 +3958,10 @@ elasticsearch:
|
|||||||
- vulnerability-mappings
|
- vulnerability-mappings
|
||||||
- common-settings
|
- common-settings
|
||||||
- common-dynamic-mappings
|
- common-dynamic-mappings
|
||||||
- logs-redis.log@package
|
|
||||||
- logs-redis.log@custom
|
|
||||||
data_stream:
|
data_stream:
|
||||||
allow_custom_routing: false
|
allow_custom_routing: false
|
||||||
hidden: false
|
hidden: false
|
||||||
ignore_missing_component_templates:
|
ignore_missing_component_templates: []
|
||||||
- logs-redis.log@custom
|
|
||||||
index_patterns:
|
index_patterns:
|
||||||
- logs-redis.log*
|
- logs-redis.log*
|
||||||
priority: 501
|
priority: 501
|
||||||
|
|||||||
+125
-16
@@ -10,6 +10,8 @@
|
|||||||
{% from 'elasticsearch/config.map.jinja' import ELASTICSEARCH_NODES %}
|
{% from 'elasticsearch/config.map.jinja' import ELASTICSEARCH_NODES %}
|
||||||
{% from 'elasticsearch/config.map.jinja' import ELASTICSEARCH_SEED_HOSTS %}
|
{% from 'elasticsearch/config.map.jinja' import ELASTICSEARCH_SEED_HOSTS %}
|
||||||
{% from 'elasticsearch/config.map.jinja' import ELASTICSEARCHMERGED %}
|
{% from 'elasticsearch/config.map.jinja' import ELASTICSEARCHMERGED %}
|
||||||
|
{% set TEMPLATES = salt['pillar.get']('elasticsearch:templates', {}) %}
|
||||||
|
{% from 'elasticsearch/template.map.jinja' import ES_INDEX_SETTINGS %}
|
||||||
|
|
||||||
include:
|
include:
|
||||||
- ca
|
- ca
|
||||||
@@ -17,9 +19,6 @@ include:
|
|||||||
- elasticsearch.ssl
|
- elasticsearch.ssl
|
||||||
- elasticsearch.config
|
- elasticsearch.config
|
||||||
- elasticsearch.sostatus
|
- elasticsearch.sostatus
|
||||||
{%- if GLOBALS.role != "so-searchnode" %}
|
|
||||||
- elasticsearch.cluster
|
|
||||||
{%- endif%}
|
|
||||||
|
|
||||||
so-elasticsearch:
|
so-elasticsearch:
|
||||||
docker_container.running:
|
docker_container.running:
|
||||||
@@ -102,24 +101,134 @@ so-elasticsearch:
|
|||||||
- cmd: auth_users_roles_inode
|
- cmd: auth_users_roles_inode
|
||||||
- cmd: auth_users_inode
|
- cmd: auth_users_inode
|
||||||
|
|
||||||
wait_for_so-elasticsearch:
|
|
||||||
http.wait_for_successful_query:
|
|
||||||
- name: "https://localhost:9200/"
|
|
||||||
- username: 'so_elastic'
|
|
||||||
- password: '{{ ELASTICSEARCHMERGED.auth.users.so_elastic_user.pass }}'
|
|
||||||
- ssl: True
|
|
||||||
- verify_ssl: False
|
|
||||||
- status: 200
|
|
||||||
- wait_for: 300
|
|
||||||
- request_interval: 15
|
|
||||||
- require:
|
|
||||||
- docker_container: so-elasticsearch
|
|
||||||
|
|
||||||
delete_so-elasticsearch_so-status.disabled:
|
delete_so-elasticsearch_so-status.disabled:
|
||||||
file.uncomment:
|
file.uncomment:
|
||||||
- name: /opt/so/conf/so-status/so-status.conf
|
- name: /opt/so/conf/so-status/so-status.conf
|
||||||
- regex: ^so-elasticsearch$
|
- regex: ^so-elasticsearch$
|
||||||
|
|
||||||
|
{% if GLOBALS.role != "so-searchnode" %}
|
||||||
|
escomponenttemplates:
|
||||||
|
file.recurse:
|
||||||
|
- name: /opt/so/conf/elasticsearch/templates/component
|
||||||
|
- source: salt://elasticsearch/templates/component
|
||||||
|
- user: 930
|
||||||
|
- group: 939
|
||||||
|
- clean: True
|
||||||
|
- onchanges_in:
|
||||||
|
- file: so-elasticsearch-templates-reload
|
||||||
|
- show_changes: False
|
||||||
|
|
||||||
|
# Auto-generate templates from defaults file
|
||||||
|
{% for index, settings in ES_INDEX_SETTINGS.items() %}
|
||||||
|
{% if settings.index_template is defined %}
|
||||||
|
es_index_template_{{index}}:
|
||||||
|
file.managed:
|
||||||
|
- name: /opt/so/conf/elasticsearch/templates/index/{{ index }}-template.json
|
||||||
|
- source: salt://elasticsearch/base-template.json.jinja
|
||||||
|
- defaults:
|
||||||
|
TEMPLATE_CONFIG: {{ settings.index_template }}
|
||||||
|
- template: jinja
|
||||||
|
- show_changes: False
|
||||||
|
- onchanges_in:
|
||||||
|
- file: so-elasticsearch-templates-reload
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if TEMPLATES %}
|
||||||
|
# Sync custom templates to /opt/so/conf/elasticsearch/templates
|
||||||
|
{% for TEMPLATE in TEMPLATES %}
|
||||||
|
es_template_{{TEMPLATE.split('.')[0] | replace("/","_") }}:
|
||||||
|
file.managed:
|
||||||
|
- source: salt://elasticsearch/templates/index/{{TEMPLATE}}
|
||||||
|
{% if 'jinja' in TEMPLATE.split('.')[-1] %}
|
||||||
|
- name: /opt/so/conf/elasticsearch/templates/index/{{TEMPLATE.split('/')[1] | replace(".jinja", "")}}
|
||||||
|
- template: jinja
|
||||||
|
{% else %}
|
||||||
|
- name: /opt/so/conf/elasticsearch/templates/index/{{TEMPLATE.split('/')[1]}}
|
||||||
|
{% endif %}
|
||||||
|
- user: 930
|
||||||
|
- group: 939
|
||||||
|
- show_changes: False
|
||||||
|
- onchanges_in:
|
||||||
|
- file: so-elasticsearch-templates-reload
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if GLOBALS.role in GLOBALS.manager_roles %}
|
||||||
|
so-es-cluster-settings:
|
||||||
|
cmd.run:
|
||||||
|
- name: /usr/sbin/so-elasticsearch-cluster-settings
|
||||||
|
- cwd: /opt/so
|
||||||
|
- template: jinja
|
||||||
|
- require:
|
||||||
|
- docker_container: so-elasticsearch
|
||||||
|
- file: elasticsearch_sbin_jinja
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
so-elasticsearch-ilm-policy-load:
|
||||||
|
cmd.run:
|
||||||
|
- name: /usr/sbin/so-elasticsearch-ilm-policy-load
|
||||||
|
- cwd: /opt/so
|
||||||
|
- require:
|
||||||
|
- docker_container: so-elasticsearch
|
||||||
|
- file: so-elasticsearch-ilm-policy-load-script
|
||||||
|
- onchanges:
|
||||||
|
- file: so-elasticsearch-ilm-policy-load-script
|
||||||
|
|
||||||
|
so-elasticsearch-templates-reload:
|
||||||
|
file.absent:
|
||||||
|
- name: /opt/so/state/estemplates.txt
|
||||||
|
|
||||||
|
so-elasticsearch-templates:
|
||||||
|
cmd.run:
|
||||||
|
- name: /usr/sbin/so-elasticsearch-templates-load
|
||||||
|
- cwd: /opt/so
|
||||||
|
- template: jinja
|
||||||
|
- require:
|
||||||
|
- docker_container: so-elasticsearch
|
||||||
|
- file: elasticsearch_sbin_jinja
|
||||||
|
|
||||||
|
so-elasticsearch-pipelines:
|
||||||
|
cmd.run:
|
||||||
|
- name: /usr/sbin/so-elasticsearch-pipelines {{ GLOBALS.hostname }}
|
||||||
|
- require:
|
||||||
|
- docker_container: so-elasticsearch
|
||||||
|
- file: so-elasticsearch-pipelines-script
|
||||||
|
|
||||||
|
so-elasticsearch-roles-load:
|
||||||
|
cmd.run:
|
||||||
|
- name: /usr/sbin/so-elasticsearch-roles-load
|
||||||
|
- cwd: /opt/so
|
||||||
|
- template: jinja
|
||||||
|
- require:
|
||||||
|
- docker_container: so-elasticsearch
|
||||||
|
- file: elasticsearch_sbin_jinja
|
||||||
|
|
||||||
|
{% if grains.role in ['so-managersearch', 'so-manager', 'so-managerhype'] %}
|
||||||
|
{% set ap = "absent" %}
|
||||||
|
{% endif %}
|
||||||
|
{% if grains.role in ['so-eval', 'so-standalone', 'so-heavynode'] %}
|
||||||
|
{% if ELASTICSEARCHMERGED.index_clean %}
|
||||||
|
{% set ap = "present" %}
|
||||||
|
{% else %}
|
||||||
|
{% set ap = "absent" %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if grains.role in ['so-eval', 'so-standalone', 'so-managersearch', 'so-heavynode', 'so-manager'] %}
|
||||||
|
so-elasticsearch-indices-delete:
|
||||||
|
cron.{{ap}}:
|
||||||
|
- name: /usr/sbin/so-elasticsearch-indices-delete > /opt/so/log/elasticsearch/cron-elasticsearch-indices-delete.log 2>&1
|
||||||
|
- identifier: so-elasticsearch-indices-delete
|
||||||
|
- user: root
|
||||||
|
- minute: '*/5'
|
||||||
|
- hour: '*'
|
||||||
|
- daymonth: '*'
|
||||||
|
- month: '*'
|
||||||
|
- dayweek: '*'
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
{{sls}}_state_not_allowed:
|
{{sls}}_state_not_allowed:
|
||||||
|
|||||||
@@ -63,8 +63,7 @@
|
|||||||
{ "set": { "if": "ctx.event?.dataset != null && !ctx.event.dataset.contains('.')", "field": "event.dataset", "value": "{{event.module}}.{{event.dataset}}" } },
|
{ "set": { "if": "ctx.event?.dataset != null && !ctx.event.dataset.contains('.')", "field": "event.dataset", "value": "{{event.module}}.{{event.dataset}}" } },
|
||||||
{ "split": { "if": "ctx.event?.dataset != null && ctx.event.dataset.contains('.')", "field": "event.dataset", "separator": "\\.", "target_field": "dataset_tag_temp" } },
|
{ "split": { "if": "ctx.event?.dataset != null && ctx.event.dataset.contains('.')", "field": "event.dataset", "separator": "\\.", "target_field": "dataset_tag_temp" } },
|
||||||
{ "append": { "if": "ctx.dataset_tag_temp != null", "field": "tags", "value": "{{dataset_tag_temp.1}}" } },
|
{ "append": { "if": "ctx.dataset_tag_temp != null", "field": "tags", "value": "{{dataset_tag_temp.1}}" } },
|
||||||
{ "grok": { "if": "ctx.http?.response?.status_code instanceof String", "field": "http.response.status_code", "patterns": ["%{NUMBER:http.response.status_code:long}(?:\\s+%{GREEDYDATA})?"], "ignore_failure": true } },
|
{ "grok": { "if": "ctx.http?.response?.status_code != null", "field": "http.response.status_code", "patterns": ["%{NUMBER:http.response.status_code:long} %{GREEDYDATA}"]} },
|
||||||
{ "convert": { "if": "ctx.http?.response?.status_code != null && !(ctx.http.response.status_code instanceof Number)", "field": "http.response.status_code", "type": "long", "ignore_failure": true } },
|
|
||||||
{ "set": { "if": "ctx?.metadata?.kafka != null" , "field": "kafka.id", "value": "{{metadata.kafka.partition}}{{metadata.kafka.offset}}{{metadata.kafka.timestamp}}", "ignore_failure": true } },
|
{ "set": { "if": "ctx?.metadata?.kafka != null" , "field": "kafka.id", "value": "{{metadata.kafka.partition}}{{metadata.kafka.offset}}{{metadata.kafka.timestamp}}", "ignore_failure": true } },
|
||||||
{ "remove": { "field": [ "message2", "type", "fields", "category", "module", "dataset", "dataset_tag_temp", "event.dataset_temp" ], "ignore_missing": true, "ignore_failure": true } },
|
{ "remove": { "field": [ "message2", "type", "fields", "category", "module", "dataset", "dataset_tag_temp", "event.dataset_temp" ], "ignore_missing": true, "ignore_failure": true } },
|
||||||
{ "pipeline": { "name": "global@custom", "ignore_missing_pipeline": true, "description": "[Fleet] Global pipeline for all data streams" } }
|
{ "pipeline": { "name": "global@custom", "ignore_missing_pipeline": true, "description": "[Fleet] Global pipeline for all data streams" } }
|
||||||
|
|||||||
@@ -177,84 +177,12 @@
|
|||||||
"description": "Extract IPs from Elastic Agent events (host.ip) and adds them to related.ip"
|
"description": "Extract IPs from Elastic Agent events (host.ip) and adds them to related.ip"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"script": {
|
|
||||||
"description": "Snapshot event.ingested into _tmp.event_ingested_pre_fleet before .fleet_final_pipeline-1 overwrites it with ES ingest time",
|
|
||||||
"lang": "painless",
|
|
||||||
"if": "ctx.event?.ingested != null && ctx.event?.created == null",
|
|
||||||
"ignore_failure": true,
|
|
||||||
"source": "ctx.putIfAbsent('_tmp', [:]); ctx._tmp.event_ingested_pre_fleet = ctx.event.ingested;"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"pipeline": {
|
"pipeline": {
|
||||||
"name": ".fleet_final_pipeline-1",
|
"name": ".fleet_final_pipeline-1",
|
||||||
"ignore_missing_pipeline": true
|
"ignore_missing_pipeline": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"script": {
|
|
||||||
"description": "Calculate time from Elastic Agent to Logstash.",
|
|
||||||
"lang": "painless",
|
|
||||||
"if": "ctx._tmp?.logstash_from_agent != null",
|
|
||||||
"ignore_failure": true,
|
|
||||||
"source": "ZonedDateTime start = ctx._tmp.event_ingested_pre_fleet != null ? ZonedDateTime.parse(ctx._tmp.event_ingested_pre_fleet) : ZonedDateTime.parse(ctx['@timestamp']); ctx.event.putIfAbsent('ingestion', [:]); ctx.event.ingestion.latency_elasticagent_to_logstash = ChronoUnit.SECONDS.between(start, ZonedDateTime.parse(ctx._tmp.logstash_from_agent));"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"script": {
|
|
||||||
"description": "Calculate time from Logstash to Redis",
|
|
||||||
"lang": "painless",
|
|
||||||
"if": "ctx._tmp?.logstash_from_agent != null && ctx._tmp?.logstash_to_redis != null",
|
|
||||||
"ignore_failure": true,
|
|
||||||
"source": "ctx.event.putIfAbsent('ingestion', [:]); ctx.event.ingestion.latency_logstash_to_redis = ChronoUnit.SECONDS.between(ZonedDateTime.parse(ctx._tmp.logstash_from_agent), ZonedDateTime.parse(ctx._tmp.logstash_to_redis));"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"script": {
|
|
||||||
"description": "Calculate time message spends in redis queue (logstash delay in pulling event).",
|
|
||||||
"lang": "painless",
|
|
||||||
"if": "ctx._tmp?.logstash_to_redis != null && ctx._tmp?.logstash_from_redis != null",
|
|
||||||
"ignore_failure": true,
|
|
||||||
"source": "ctx.event.putIfAbsent('ingestion', [:]); ctx.event.ingestion.latency_redis_to_logstash = ChronoUnit.SECONDS.between(ZonedDateTime.parse(ctx._tmp.logstash_to_redis), ZonedDateTime.parse(ctx._tmp.logstash_from_redis));"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"script": {
|
|
||||||
"description": "Calculate time from Logstash to Elasticsearch (after read from Redis).",
|
|
||||||
"lang": "painless",
|
|
||||||
"if": "ctx._tmp?.logstash_from_redis != null",
|
|
||||||
"ignore_failure": true,
|
|
||||||
"source": "ctx.event.putIfAbsent('ingestion', [:]); ctx.event.ingestion.latency_logstash_to_elasticsearch = ChronoUnit.SECONDS.between(ZonedDateTime.parse(ctx._tmp.logstash_from_redis), metadata().now);"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"script": {
|
|
||||||
"description": "Calculate time from Elastic Agent to Kafka.",
|
|
||||||
"lang": "painless",
|
|
||||||
"if": "ctx._tmp?.logstash_from_kafka != null && ctx._tmp?.logstash_from_agent == null",
|
|
||||||
"ignore_failure": true,
|
|
||||||
"source": "ZonedDateTime start = ctx._tmp.event_ingested_pre_fleet != null ? ZonedDateTime.parse(ctx._tmp.event_ingested_pre_fleet) : ZonedDateTime.parse(ctx['@timestamp']); ctx.event.putIfAbsent('ingestion', [:]); ctx.event.ingestion.latency_elasticagent_to_kafka = ChronoUnit.SECONDS.between(start, ZonedDateTime.parse(ctx._tmp.logstash_from_kafka));"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"script": {
|
|
||||||
"description": "Calculate time message spends in Kafka queue (logstash delay in pulling event).",
|
|
||||||
"lang": "painless",
|
|
||||||
"if": "ctx._tmp?.logstash_from_kafka != null && ctx.metadata?.kafka?.timestamp != null && ctx._tmp?.logstash_from_agent == null",
|
|
||||||
"ignore_failure": true,
|
|
||||||
"source": "ctx.event.putIfAbsent('ingestion', [:]); ctx.event.ingestion.latency_kafka_queue = ChronoUnit.SECONDS.between(ZonedDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(ctx.metadata.kafka.timestamp.toString())), ZoneId.of('UTC')), ZonedDateTime.parse(ctx._tmp.logstash_from_kafka));"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"script": {
|
|
||||||
"description": "Calculate time from Logstash to Elasticsearch (after read from Kafka).",
|
|
||||||
"lang": "painless",
|
|
||||||
"if": "ctx._tmp?.logstash_from_kafka != null && ctx._tmp?.logstash_from_agent == null",
|
|
||||||
"ignore_failure": true,
|
|
||||||
"source": "ctx.event.putIfAbsent('ingestion', [:]); ctx.event.ingestion.latency_kafka_to_elasticsearch = ChronoUnit.SECONDS.between(ZonedDateTime.parse(ctx._tmp.logstash_from_kafka), metadata().now);"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"remove": {
|
"remove": {
|
||||||
"field": "event.agent_id_status",
|
"field": "event.agent_id_status",
|
||||||
@@ -274,12 +202,11 @@
|
|||||||
"event.dataset_temp",
|
"event.dataset_temp",
|
||||||
"dataset_tag_temp",
|
"dataset_tag_temp",
|
||||||
"module_temp",
|
"module_temp",
|
||||||
"datastream_dataset_temp",
|
"datastream_dataset_temp"
|
||||||
"_tmp"
|
|
||||||
],
|
],
|
||||||
"ignore_missing": true,
|
"ignore_missing": true,
|
||||||
"ignore_failure": true
|
"ignore_failure": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
+13
-74
@@ -10,28 +10,24 @@
|
|||||||
"processors": [
|
"processors": [
|
||||||
{
|
{
|
||||||
"set": {
|
"set": {
|
||||||
"tag": "set_ecs_version_f5923549",
|
|
||||||
"field": "ecs.version",
|
"field": "ecs.version",
|
||||||
"value": "8.17.0"
|
"value": "8.17.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"set": {
|
"set": {
|
||||||
"tag": "set_observer_vendor_ad9d35cc",
|
|
||||||
"field": "observer.vendor",
|
"field": "observer.vendor",
|
||||||
"value": "netgate"
|
"value": "netgate"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"set": {
|
"set": {
|
||||||
"tag": "set_observer_type_5dddf3ba",
|
|
||||||
"field": "observer.type",
|
"field": "observer.type",
|
||||||
"value": "firewall"
|
"value": "firewall"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rename": {
|
"rename": {
|
||||||
"tag": "rename_message_to_event_original_56a77271",
|
|
||||||
"field": "message",
|
"field": "message",
|
||||||
"target_field": "event.original",
|
"target_field": "event.original",
|
||||||
"ignore_missing": true,
|
"ignore_missing": true,
|
||||||
@@ -40,14 +36,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"set": {
|
"set": {
|
||||||
"tag": "set_event_kind_de80643c",
|
|
||||||
"field": "event.kind",
|
"field": "event.kind",
|
||||||
"value": "event"
|
"value": "event"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"set": {
|
"set": {
|
||||||
"tag": "set_event_timezone_4ca44cac",
|
|
||||||
"field": "event.timezone",
|
"field": "event.timezone",
|
||||||
"value": "{{{_tmp.tz_offset}}}",
|
"value": "{{{_tmp.tz_offset}}}",
|
||||||
"if": "ctx._tmp?.tz_offset != null && ctx._tmp?.tz_offset != 'local'"
|
"if": "ctx._tmp?.tz_offset != null && ctx._tmp?.tz_offset != 'local'"
|
||||||
@@ -55,7 +49,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"grok": {
|
"grok": {
|
||||||
"tag": "grok_event_original_27d9c8c7",
|
|
||||||
"description": "Parse syslog header",
|
"description": "Parse syslog header",
|
||||||
"field": "event.original",
|
"field": "event.original",
|
||||||
"patterns": [
|
"patterns": [
|
||||||
@@ -79,7 +72,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"date": {
|
"date": {
|
||||||
"tag": "date__tmp_timestamp8601_to_timestamp_6ac9d3ce",
|
|
||||||
"if": "ctx._tmp.timestamp8601 != null",
|
"if": "ctx._tmp.timestamp8601 != null",
|
||||||
"field": "_tmp.timestamp8601",
|
"field": "_tmp.timestamp8601",
|
||||||
"target_field": "@timestamp",
|
"target_field": "@timestamp",
|
||||||
@@ -90,7 +82,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"date": {
|
"date": {
|
||||||
"tag": "date__tmp_timestamp_to_timestamp_f21e536e",
|
|
||||||
"if": "ctx.event?.timezone != null && ctx._tmp?.timestamp != null",
|
"if": "ctx.event?.timezone != null && ctx._tmp?.timestamp != null",
|
||||||
"field": "_tmp.timestamp",
|
"field": "_tmp.timestamp",
|
||||||
"target_field": "@timestamp",
|
"target_field": "@timestamp",
|
||||||
@@ -104,7 +95,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"grok": {
|
"grok": {
|
||||||
"tag": "grok_process_name_cef3d489",
|
|
||||||
"description": "Set Event Provider",
|
"description": "Set Event Provider",
|
||||||
"field": "process.name",
|
"field": "process.name",
|
||||||
"patterns": [
|
"patterns": [
|
||||||
@@ -117,83 +107,71 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pipeline": {
|
"pipeline": {
|
||||||
"tag": "pipeline_e16851a7",
|
"name": "logs-pfsense.log-1.23.1-firewall",
|
||||||
"name": "logs-pfsense.log-1.25.2-firewall",
|
|
||||||
"if": "ctx.event.provider == 'filterlog'"
|
"if": "ctx.event.provider == 'filterlog'"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pipeline": {
|
"pipeline": {
|
||||||
"tag": "pipeline_828590b5",
|
"name": "logs-pfsense.log-1.23.1-openvpn",
|
||||||
"name": "logs-pfsense.log-1.25.2-openvpn",
|
|
||||||
"if": "ctx.event.provider == 'openvpn'"
|
"if": "ctx.event.provider == 'openvpn'"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pipeline": {
|
"pipeline": {
|
||||||
"tag": "pipeline_9d37039c",
|
"name": "logs-pfsense.log-1.23.1-ipsec",
|
||||||
"name": "logs-pfsense.log-1.25.2-ipsec",
|
|
||||||
"if": "ctx.event.provider == 'charon'"
|
"if": "ctx.event.provider == 'charon'"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pipeline": {
|
"pipeline": {
|
||||||
"tag": "pipeline_ad56bbca",
|
"name": "logs-pfsense.log-1.23.1-dhcp",
|
||||||
"name": "logs-pfsense.log-1.25.2-dhcp",
|
"if": "[\"dhcpd\", \"dhclient\", \"dhcp6c\"].contains(ctx.event.provider)"
|
||||||
"if": "[\"dhcpd\", \"dhclient\", \"dhcp6c\", \"dnsmasq-dhcp\"].contains(ctx.event.provider)"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pipeline": {
|
"pipeline": {
|
||||||
"tag": "pipeline_dd85553d",
|
"name": "logs-pfsense.log-1.23.1-unbound",
|
||||||
"name": "logs-pfsense.log-1.25.2-unbound",
|
|
||||||
"if": "ctx.event.provider == 'unbound'"
|
"if": "ctx.event.provider == 'unbound'"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pipeline": {
|
"pipeline": {
|
||||||
"tag": "pipeline_720ed255",
|
"name": "logs-pfsense.log-1.23.1-haproxy",
|
||||||
"name": "logs-pfsense.log-1.25.2-haproxy",
|
|
||||||
"if": "ctx.event.provider == 'haproxy'"
|
"if": "ctx.event.provider == 'haproxy'"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pipeline": {
|
"pipeline": {
|
||||||
"tag": "pipeline_456beba5",
|
"name": "logs-pfsense.log-1.23.1-php-fpm",
|
||||||
"name": "logs-pfsense.log-1.25.2-php-fpm",
|
|
||||||
"if": "ctx.event.provider == 'php-fpm'"
|
"if": "ctx.event.provider == 'php-fpm'"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pipeline": {
|
"pipeline": {
|
||||||
"tag": "pipeline_a0d89375",
|
"name": "logs-pfsense.log-1.23.1-squid",
|
||||||
"name": "logs-pfsense.log-1.25.2-squid",
|
|
||||||
"if": "ctx.event.provider == 'squid'"
|
"if": "ctx.event.provider == 'squid'"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pipeline": {
|
"pipeline": {
|
||||||
"tag": "pipeline_c2f1ed55",
|
"name": "logs-pfsense.log-1.23.1-snort",
|
||||||
"name": "logs-pfsense.log-1.25.2-snort",
|
|
||||||
"if": "ctx.event.provider == 'snort'"
|
"if": "ctx.event.provider == 'snort'"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pipeline": {
|
"pipeline": {
|
||||||
"tag":"pipeline_33db1c9e",
|
"name": "logs-pfsense.log-1.23.1-suricata",
|
||||||
"name": "logs-pfsense.log-1.25.2-suricata",
|
|
||||||
"if": "ctx.event.provider == 'suricata'"
|
"if": "ctx.event.provider == 'suricata'"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"drop": {
|
"drop": {
|
||||||
"tag": "drop_9d7c46f8",
|
"if": "![\"filterlog\", \"openvpn\", \"charon\", \"dhcpd\", \"dhclient\", \"dhcp6c\", \"unbound\", \"haproxy\", \"php-fpm\", \"squid\", \"snort\", \"suricata\"].contains(ctx.event?.provider)"
|
||||||
"if": "![\"filterlog\", \"openvpn\", \"charon\", \"dhcpd\", \"dnsmasq-dhcp\", \"dhclient\", \"dhcp6c\", \"unbound\", \"haproxy\", \"php-fpm\", \"squid\", \"snort\", \"suricata\"].contains(ctx.event?.provider)"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"append": {
|
"append": {
|
||||||
"tag": "append_event_category_4780a983",
|
|
||||||
"field": "event.category",
|
"field": "event.category",
|
||||||
"value": "network",
|
"value": "network",
|
||||||
"if": "ctx.network != null"
|
"if": "ctx.network != null"
|
||||||
@@ -201,7 +179,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"convert": {
|
"convert": {
|
||||||
"tag": "convert_source_address_to_source_ip_f5632a20",
|
|
||||||
"field": "source.address",
|
"field": "source.address",
|
||||||
"target_field": "source.ip",
|
"target_field": "source.ip",
|
||||||
"type": "ip",
|
"type": "ip",
|
||||||
@@ -211,7 +188,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"convert": {
|
"convert": {
|
||||||
"tag": "convert_destination_address_to_destination_ip_f1388f0c",
|
|
||||||
"field": "destination.address",
|
"field": "destination.address",
|
||||||
"target_field": "destination.ip",
|
"target_field": "destination.ip",
|
||||||
"type": "ip",
|
"type": "ip",
|
||||||
@@ -221,7 +197,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"set": {
|
"set": {
|
||||||
"tag": "set_network_type_1f1d940a",
|
|
||||||
"field": "network.type",
|
"field": "network.type",
|
||||||
"value": "ipv6",
|
"value": "ipv6",
|
||||||
"if": "ctx.source?.ip != null && ctx.source.ip.contains(\":\")"
|
"if": "ctx.source?.ip != null && ctx.source.ip.contains(\":\")"
|
||||||
@@ -229,7 +204,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"set": {
|
"set": {
|
||||||
"tag": "set_network_type_69deca38",
|
|
||||||
"field": "network.type",
|
"field": "network.type",
|
||||||
"value": "ipv4",
|
"value": "ipv4",
|
||||||
"if": "ctx.source?.ip != null && ctx.source.ip.contains(\".\")"
|
"if": "ctx.source?.ip != null && ctx.source.ip.contains(\".\")"
|
||||||
@@ -237,7 +211,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"geoip": {
|
"geoip": {
|
||||||
"tag": "geoip_source_ip_to_source_geo_da2e41b2",
|
|
||||||
"field": "source.ip",
|
"field": "source.ip",
|
||||||
"target_field": "source.geo",
|
"target_field": "source.geo",
|
||||||
"ignore_missing": true
|
"ignore_missing": true
|
||||||
@@ -245,7 +218,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"geoip": {
|
"geoip": {
|
||||||
"tag": "geoip_destination_ip_to_destination_geo_ab5e2968",
|
|
||||||
"field": "destination.ip",
|
"field": "destination.ip",
|
||||||
"target_field": "destination.geo",
|
"target_field": "destination.geo",
|
||||||
"ignore_missing": true
|
"ignore_missing": true
|
||||||
@@ -253,7 +225,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"geoip": {
|
"geoip": {
|
||||||
"tag": "geoip_source_ip_to_source_as_28d69883",
|
|
||||||
"ignore_missing": true,
|
"ignore_missing": true,
|
||||||
"database_file": "GeoLite2-ASN.mmdb",
|
"database_file": "GeoLite2-ASN.mmdb",
|
||||||
"field": "source.ip",
|
"field": "source.ip",
|
||||||
@@ -266,7 +237,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"geoip": {
|
"geoip": {
|
||||||
"tag": "geoip_destination_ip_to_destination_as_8a007787",
|
|
||||||
"database_file": "GeoLite2-ASN.mmdb",
|
"database_file": "GeoLite2-ASN.mmdb",
|
||||||
"field": "destination.ip",
|
"field": "destination.ip",
|
||||||
"target_field": "destination.as",
|
"target_field": "destination.as",
|
||||||
@@ -279,7 +249,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rename": {
|
"rename": {
|
||||||
"tag": "rename_source_as_asn_to_source_as_number_a917047d",
|
|
||||||
"field": "source.as.asn",
|
"field": "source.as.asn",
|
||||||
"target_field": "source.as.number",
|
"target_field": "source.as.number",
|
||||||
"ignore_missing": true
|
"ignore_missing": true
|
||||||
@@ -287,7 +256,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rename": {
|
"rename": {
|
||||||
"tag": "rename_source_as_organization_name_to_source_as_organization_name_f1362d0b",
|
|
||||||
"field": "source.as.organization_name",
|
"field": "source.as.organization_name",
|
||||||
"target_field": "source.as.organization.name",
|
"target_field": "source.as.organization.name",
|
||||||
"ignore_missing": true
|
"ignore_missing": true
|
||||||
@@ -295,7 +263,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rename": {
|
"rename": {
|
||||||
"tag": "rename_destination_as_asn_to_destination_as_number_3b459fcd",
|
|
||||||
"field": "destination.as.asn",
|
"field": "destination.as.asn",
|
||||||
"target_field": "destination.as.number",
|
"target_field": "destination.as.number",
|
||||||
"ignore_missing": true
|
"ignore_missing": true
|
||||||
@@ -303,7 +270,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rename": {
|
"rename": {
|
||||||
"tag": "rename_destination_as_organization_name_to_destination_as_organization_name_814bd459",
|
|
||||||
"field": "destination.as.organization_name",
|
"field": "destination.as.organization_name",
|
||||||
"target_field": "destination.as.organization.name",
|
"target_field": "destination.as.organization.name",
|
||||||
"ignore_missing": true
|
"ignore_missing": true
|
||||||
@@ -311,14 +277,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"community_id": {
|
"community_id": {
|
||||||
"tag": "community_id_d2308e7a",
|
|
||||||
"target_field": "network.community_id",
|
"target_field": "network.community_id",
|
||||||
"ignore_failure": true
|
"ignore_failure": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"grok": {
|
"grok": {
|
||||||
"tag": "grok_observer_ingress_interface_name_968018d3",
|
|
||||||
"field": "observer.ingress.interface.name",
|
"field": "observer.ingress.interface.name",
|
||||||
"patterns": [
|
"patterns": [
|
||||||
"%{DATA}.%{NONNEGINT:observer.ingress.vlan.id}"
|
"%{DATA}.%{NONNEGINT:observer.ingress.vlan.id}"
|
||||||
@@ -329,7 +293,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"set": {
|
"set": {
|
||||||
"tag": "set_network_vlan_id_efd4d96a",
|
|
||||||
"field": "network.vlan.id",
|
"field": "network.vlan.id",
|
||||||
"copy_from": "observer.ingress.vlan.id",
|
"copy_from": "observer.ingress.vlan.id",
|
||||||
"ignore_empty_value": true
|
"ignore_empty_value": true
|
||||||
@@ -337,7 +300,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"append": {
|
"append": {
|
||||||
"tag": "append_related_ip_c1a6356b",
|
|
||||||
"field": "related.ip",
|
"field": "related.ip",
|
||||||
"value": "{{{destination.ip}}}",
|
"value": "{{{destination.ip}}}",
|
||||||
"allow_duplicates": false,
|
"allow_duplicates": false,
|
||||||
@@ -346,7 +308,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"append": {
|
"append": {
|
||||||
"tag": "append_related_ip_8121c591",
|
|
||||||
"field": "related.ip",
|
"field": "related.ip",
|
||||||
"value": "{{{source.ip}}}",
|
"value": "{{{source.ip}}}",
|
||||||
"allow_duplicates": false,
|
"allow_duplicates": false,
|
||||||
@@ -355,7 +316,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"append": {
|
"append": {
|
||||||
"tag": "append_related_ip_53b62ed8",
|
|
||||||
"field": "related.ip",
|
"field": "related.ip",
|
||||||
"value": "{{{source.nat.ip}}}",
|
"value": "{{{source.nat.ip}}}",
|
||||||
"allow_duplicates": false,
|
"allow_duplicates": false,
|
||||||
@@ -364,7 +324,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"append": {
|
"append": {
|
||||||
"tag": "append_related_hosts_6f162628",
|
|
||||||
"field": "related.hosts",
|
"field": "related.hosts",
|
||||||
"value": "{{{destination.domain}}}",
|
"value": "{{{destination.domain}}}",
|
||||||
"if": "ctx.destination?.domain != null"
|
"if": "ctx.destination?.domain != null"
|
||||||
@@ -372,7 +331,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"append": {
|
"append": {
|
||||||
"tag": "append_related_user_c036eec2",
|
|
||||||
"field": "related.user",
|
"field": "related.user",
|
||||||
"value": "{{{user.name}}}",
|
"value": "{{{user.name}}}",
|
||||||
"if": "ctx.user?.name != null"
|
"if": "ctx.user?.name != null"
|
||||||
@@ -380,7 +338,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"set": {
|
"set": {
|
||||||
"tag": "set_network_direction_cb1e3125",
|
|
||||||
"field": "network.direction",
|
"field": "network.direction",
|
||||||
"value": "{{{network.direction}}}bound",
|
"value": "{{{network.direction}}}bound",
|
||||||
"if": "ctx.network?.direction != null && ctx.network?.direction =~ /^(in|out)$/"
|
"if": "ctx.network?.direction != null && ctx.network?.direction =~ /^(in|out)$/"
|
||||||
@@ -388,7 +345,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"remove": {
|
"remove": {
|
||||||
"tag": "remove_a82e20f2",
|
|
||||||
"field": [
|
"field": [
|
||||||
"_tmp"
|
"_tmp"
|
||||||
],
|
],
|
||||||
@@ -397,21 +353,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"script": {
|
"script": {
|
||||||
"tag": "script_a7f2c062",
|
|
||||||
"lang": "painless",
|
"lang": "painless",
|
||||||
"description": "This script processor iterates over the whole document to remove fields with null values.",
|
"description": "This script processor iterates over the whole document to remove fields with null values.",
|
||||||
"source": "void handleMap(Map map) {\n for (def x : map.values()) {\n if (x instanceof Map) {\n handleMap(x);\n } else if (x instanceof List) {\n handleList(x);\n }\n }\n map.values().removeIf(v -> v == null || (v instanceof String && v == \"-\"));\n}\nvoid handleList(List list) {\n for (def x : list) {\n if (x instanceof Map) {\n handleMap(x);\n } else if (x instanceof List) {\n handleList(x);\n }\n }\n}\nhandleMap(ctx);\n"
|
"source": "void handleMap(Map map) {\n for (def x : map.values()) {\n if (x instanceof Map) {\n handleMap(x);\n } else if (x instanceof List) {\n handleList(x);\n }\n }\n map.values().removeIf(v -> v == null || (v instanceof String && v == \"-\"));\n}\nvoid handleList(List list) {\n for (def x : list) {\n if (x instanceof Map) {\n handleMap(x);\n } else if (x instanceof List) {\n handleList(x);\n }\n }\n}\nhandleMap(ctx);\n"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"append": {
|
|
||||||
"tag": "append_preserve_original_event_on_error",
|
|
||||||
"field": "tags",
|
|
||||||
"value": "preserve_original_event",
|
|
||||||
"allow_duplicates": false,
|
|
||||||
"if": "ctx.error?.message != null"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"pipeline": {
|
"pipeline": {
|
||||||
"name": "global@custom",
|
"name": "global@custom",
|
||||||
@@ -459,14 +405,7 @@
|
|||||||
{
|
{
|
||||||
"append": {
|
"append": {
|
||||||
"field": "error.message",
|
"field": "error.message",
|
||||||
"value": "Processor '{{{ _ingest.on_failure_processor_type }}}' {{#_ingest.on_failure_processor_tag}}with tag '{{{ _ingest.on_failure_processor_tag }}}' {{/_ingest.on_failure_processor_tag}}in pipeline '{{{ _ingest.pipeline }}}' failed with message '{{{ _ingest.on_failure_message }}}'"
|
"value": "{{{ _ingest.on_failure_message }}}"
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"append": {
|
|
||||||
"field": "tags",
|
|
||||||
"value": "preserve_original_event",
|
|
||||||
"allow_duplicates": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
{
|
|
||||||
"description": "zeek.ja4d",
|
|
||||||
"processors": [
|
|
||||||
{
|
|
||||||
"set": {
|
|
||||||
"field": "event.dataset",
|
|
||||||
"value": "ja4d"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"remove": {
|
|
||||||
"field": [
|
|
||||||
"host"
|
|
||||||
],
|
|
||||||
"ignore_failure": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"json": {
|
|
||||||
"field": "message",
|
|
||||||
"target_field": "message2",
|
|
||||||
"ignore_failure": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rename": {
|
|
||||||
"field": "message2.ja4d",
|
|
||||||
"target_field": "hash.ja4d",
|
|
||||||
"ignore_missing": true,
|
|
||||||
"if": "ctx?.message2?.ja4d != null && ctx.message2.ja4d.length() > 0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rename": {
|
|
||||||
"field": "message2.client_mac",
|
|
||||||
"target_field": "host.mac",
|
|
||||||
"ignore_missing": true,
|
|
||||||
"if": "ctx?.message2?.client_mac != null && ctx.message2.client_mac.length() > 0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rename": {
|
|
||||||
"field": "message2.hostname",
|
|
||||||
"target_field": "host.hostname",
|
|
||||||
"ignore_missing": true,
|
|
||||||
"if": "ctx?.message2?.hostname != null && ctx.message2.hostname.length() > 0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rename": {
|
|
||||||
"field": "message2.requested_ip",
|
|
||||||
"target_field": "dhcp.requested_address",
|
|
||||||
"ignore_missing": true,
|
|
||||||
"if": "ctx?.message2?.requested_ip != null && ctx.message2.requested_ip.length() > 0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rename": {
|
|
||||||
"field": "message2.vendor_class_id",
|
|
||||||
"target_field": "zeek.ja4d.vendor_class_id",
|
|
||||||
"ignore_missing": true,
|
|
||||||
"if": "ctx?.message2?.vendor_class_id != null && ctx.message2.vendor_class_id.length() > 0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"pipeline": {
|
|
||||||
"name": "zeek.common"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -45,7 +45,3 @@ appender.rolling_json.strategy.action.condition.nested_condition.age = 1D
|
|||||||
rootLogger.level = info
|
rootLogger.level = info
|
||||||
rootLogger.appenderRef.rolling.ref = rolling
|
rootLogger.appenderRef.rolling.ref = rolling
|
||||||
rootLogger.appenderRef.rolling_json.ref = rolling_json
|
rootLogger.appenderRef.rolling_json.ref = rolling_json
|
||||||
|
|
||||||
# Suppress NotEntitledException WARNs (ES 9.3.3 bug)
|
|
||||||
logger.entitlement_security.name = org.elasticsearch.entitlement.runtime.policy.PolicyManager.x-pack-security.org.elasticsearch.security.org.elasticsearch.xpack.security
|
|
||||||
logger.entitlement_security.level = error
|
|
||||||
@@ -14,42 +14,15 @@
|
|||||||
|
|
||||||
{% set ES_INDEX_SETTINGS_ORIG = ELASTICSEARCHDEFAULTS.elasticsearch.index_settings %}
|
{% set ES_INDEX_SETTINGS_ORIG = ELASTICSEARCHDEFAULTS.elasticsearch.index_settings %}
|
||||||
|
|
||||||
{% set ALL_ADDON_INTEGRATION_DEFAULTS = {} %}
|
|
||||||
{% set ALL_ADDON_SETTINGS_ORIG = {} %}
|
|
||||||
{% set ALL_ADDON_SETTINGS_GLOBAL_OVERRIDES = {} %}
|
|
||||||
{% set ALL_ADDON_SETTINGS = {} %}
|
|
||||||
{# start generation of integration default index_settings #}
|
{# start generation of integration default index_settings #}
|
||||||
{% if salt['file.file_exists']('/opt/so/state/esfleet_component_templates.json') %}
|
{% if salt['file.file_exists']('/opt/so/state/esfleet_package_components.json') and salt['file.file_exists']('/opt/so/state/esfleet_component_templates.json') %}
|
||||||
{# import integration type defaults #}
|
{% set check_package_components = salt['file.stats']('/opt/so/state/esfleet_package_components.json') %}
|
||||||
{% if salt['file.file_exists']('/opt/so/state/esfleet_package_components.json') %}
|
{% if check_package_components.size > 1 %}
|
||||||
{% set check_integration_package_components = salt['file.stats']('/opt/so/state/esfleet_package_components.json') %}
|
{% from 'elasticfleet/integration-defaults.map.jinja' import ADDON_INTEGRATION_DEFAULTS %}
|
||||||
{% if check_integration_package_components.size > 1 %}
|
{% for index, settings in ADDON_INTEGRATION_DEFAULTS.items() %}
|
||||||
{% from 'elasticfleet/integration-defaults.map.jinja' import ADDON_INTEGRATION_DEFAULTS %}
|
{% do ES_INDEX_SETTINGS_ORIG.update({index: settings}) %}
|
||||||
{% do ALL_ADDON_INTEGRATION_DEFAULTS.update(ADDON_INTEGRATION_DEFAULTS) %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif%}
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{# import input type defaults #}
|
|
||||||
{% if salt['file.file_exists']('/opt/so/state/esfleet_input_package_components.json') %}
|
|
||||||
{% set check_input_package_components = salt['file.stats']('/opt/so/state/esfleet_input_package_components.json') %}
|
|
||||||
{% if check_input_package_components.size > 1 %}
|
|
||||||
{% from 'elasticfleet/input-defaults.map.jinja' import ADDON_INPUT_INTEGRATION_DEFAULTS %}
|
|
||||||
{% do ALL_ADDON_INTEGRATION_DEFAULTS.update(ADDON_INPUT_INTEGRATION_DEFAULTS) %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{# import content type defaults #}
|
|
||||||
{% if salt['file.file_exists']('/opt/so/state/esfleet_content_package_components.json') %}
|
|
||||||
{% set check_content_package_components = salt['file.stats']('/opt/so/state/esfleet_content_package_components.json') %}
|
|
||||||
{% if check_content_package_components.size > 1 %}
|
|
||||||
{% from 'elasticfleet/content-defaults.map.jinja' import ADDON_CONTENT_INTEGRATION_DEFAULTS %}
|
|
||||||
{% do ALL_ADDON_INTEGRATION_DEFAULTS.update(ADDON_CONTENT_INTEGRATION_DEFAULTS) %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% for index, settings in ALL_ADDON_INTEGRATION_DEFAULTS.items() %}
|
|
||||||
{% do ALL_ADDON_SETTINGS_ORIG.update({index: settings}) %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{# end generation of integration default index_settings #}
|
{# end generation of integration default index_settings #}
|
||||||
|
|
||||||
@@ -58,43 +31,25 @@
|
|||||||
{% do ES_INDEX_SETTINGS_GLOBAL_OVERRIDES.update({index: salt['defaults.merge'](ELASTICSEARCHDEFAULTS.elasticsearch.index_settings[index], PILLAR_GLOBAL_OVERRIDES, in_place=False)}) %}
|
{% do ES_INDEX_SETTINGS_GLOBAL_OVERRIDES.update({index: salt['defaults.merge'](ELASTICSEARCHDEFAULTS.elasticsearch.index_settings[index], PILLAR_GLOBAL_OVERRIDES, in_place=False)}) %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% if ALL_ADDON_SETTINGS_ORIG.keys() | length > 0 %}
|
|
||||||
{% 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)}) %}
|
|
||||||
{# 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 %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% set ES_INDEX_SETTINGS = {} %}
|
{% set ES_INDEX_SETTINGS = {} %}
|
||||||
{% macro create_final_index_template(DEFINED_SETTINGS, GLOBAL_OVERRIDES, FINAL_INDEX_SETTINGS, EXCLUDE_INDICES=[]) %}
|
{% do ES_INDEX_SETTINGS_GLOBAL_OVERRIDES.update(salt['defaults.merge'](ES_INDEX_SETTINGS_GLOBAL_OVERRIDES, ES_INDEX_PILLAR, in_place=False)) %}
|
||||||
|
{% for index, settings in ES_INDEX_SETTINGS_GLOBAL_OVERRIDES.items() %}
|
||||||
{% do GLOBAL_OVERRIDES.update(salt['defaults.merge'](GLOBAL_OVERRIDES, ES_INDEX_PILLAR, in_place=False)) %}
|
|
||||||
{% 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 ES_INDEX_SETTINGS_ORIG and index in ES_INDEX_SETTINGS_GLOBAL_OVERRIDES %}
|
||||||
|
|
||||||
{# dont merge policy from the global_overrides if policy isn't defined in the original index settingss #}
|
{# dont merge policy from the global_overrides if policy isn't defined in the original index settingss #}
|
||||||
{# this will prevent so-elasticsearch-ilm-policy-load from trying to load policy on non ILM manged indices #}
|
{# this will prevent so-elasticsearch-ilm-policy-load from trying to load policy on non ILM manged indices #}
|
||||||
{% if not DEFINED_SETTINGS[index].policy is defined and GLOBAL_OVERRIDES[index].policy is defined %}
|
{% if not ES_INDEX_SETTINGS_ORIG[index].policy is defined and ES_INDEX_SETTINGS_GLOBAL_OVERRIDES[index].policy is defined %}
|
||||||
{% do GLOBAL_OVERRIDES[index].pop('policy') %}
|
{% do ES_INDEX_SETTINGS_GLOBAL_OVERRIDES[index].pop('policy') %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# this prevents and index from inderiting a policy phase from global overrides if it wasnt defined in the defaults. #}
|
{# this prevents and index from inderiting a policy phase from global overrides if it wasnt defined in the defaults. #}
|
||||||
{% if GLOBAL_OVERRIDES[index].policy is defined %}
|
{% if ES_INDEX_SETTINGS_GLOBAL_OVERRIDES[index].policy is defined %}
|
||||||
{% for phase in GLOBAL_OVERRIDES[index].policy.phases.copy() %}
|
{% for phase in ES_INDEX_SETTINGS_GLOBAL_OVERRIDES[index].policy.phases.copy() %}
|
||||||
{% if DEFINED_SETTINGS[index].policy.phases[phase] is not defined %}
|
{% if ES_INDEX_SETTINGS_ORIG[index].policy.phases[phase] is not defined %}
|
||||||
{% do GLOBAL_OVERRIDES[index].policy.phases.pop(phase) %}
|
{% do ES_INDEX_SETTINGS_GLOBAL_OVERRIDES[index].policy.phases.pop(phase) %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -156,23 +111,5 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% do FINAL_INDEX_SETTINGS.update({index | replace("_x_", "."): GLOBAL_OVERRIDES[index]}) %}
|
{% do ES_INDEX_SETTINGS.update({index | replace("_x_", "."): ES_INDEX_SETTINGS_GLOBAL_OVERRIDES[index]}) %}
|
||||||
{% endfor %}
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{# Exclude addon integrations from final ES_INDEX_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 = [] %}
|
|
||||||
{% for index, settings in ES_INDEX_SETTINGS.items() %}
|
|
||||||
{% do SO_MANAGED_INDICES.append(index) %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% set ADDON_INDICES = [] %}
|
|
||||||
{% for index, settings in ALL_ADDON_SETTINGS.items() %}
|
|
||||||
{% do ADDON_INDICES.append(index) %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -6,19 +6,8 @@
|
|||||||
# Elastic License 2.0.
|
# Elastic License 2.0.
|
||||||
|
|
||||||
. /usr/sbin/so-common
|
. /usr/sbin/so-common
|
||||||
|
if [ "$1" == "" ]; then
|
||||||
if [[ -z "$1" ]]; then
|
curl -K /opt/so/conf/elasticsearch/curl.config -s -k -L https://localhost:9200/_component_template | jq '.component_templates[] |.name'| sort
|
||||||
if output=$(so-elasticsearch-query "_component_template" --retry 3 --retry-delay 1 --fail); then
|
|
||||||
jq '[.component_templates[] | .name] | sort' <<< "$output"
|
|
||||||
else
|
|
||||||
echo "Failed to retrieve component templates from Elasticsearch."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
if output=$(so-elasticsearch-query "_component_template/$1" --retry 3 --retry-delay 1 --fail); then
|
curl -K /opt/so/conf/elasticsearch/curl.config -s -k -L https://localhost:9200/_component_template/$1 | jq
|
||||||
jq <<< "$output"
|
fi
|
||||||
else
|
|
||||||
echo "Failed to retrieve component template '$1' from Elasticsearch."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|||||||
@@ -1,276 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
|
|
||||||
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
|
|
||||||
# https://securityonion.net/license; you may not use this file except in compliance with the
|
|
||||||
# Elastic License 2.0.
|
|
||||||
|
|
||||||
. /usr/sbin/so-common
|
|
||||||
|
|
||||||
SO_STATEFILE_SUCCESS=/opt/so/state/estemplates.txt
|
|
||||||
ADDON_STATEFILE_SUCCESS=/opt/so/state/addon_estemplates.txt
|
|
||||||
ELASTICSEARCH_TEMPLATES_DIR="/opt/so/conf/elasticsearch/templates"
|
|
||||||
SO_TEMPLATES_DIR="${ELASTICSEARCH_TEMPLATES_DIR}/index"
|
|
||||||
ADDON_TEMPLATES_DIR="${ELASTICSEARCH_TEMPLATES_DIR}/addon-index"
|
|
||||||
SO_LOAD_FAILURES=0
|
|
||||||
ADDON_LOAD_FAILURES=0
|
|
||||||
SO_LOAD_FAILURES_NAMES=()
|
|
||||||
ADDON_LOAD_FAILURES_NAMES=()
|
|
||||||
IS_HEAVYNODE="false"
|
|
||||||
FORCE="false"
|
|
||||||
VERBOSE="false"
|
|
||||||
SHOULD_EXIT_ON_FAILURE="true"
|
|
||||||
|
|
||||||
# If soup is running, ignore errors
|
|
||||||
pgrep soup >/dev/null && SHOULD_EXIT_ON_FAILURE="false"
|
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
|
||||||
case "$1" in
|
|
||||||
--heavynode)
|
|
||||||
IS_HEAVYNODE="true"
|
|
||||||
;;
|
|
||||||
--force)
|
|
||||||
FORCE="true"
|
|
||||||
;;
|
|
||||||
--verbose)
|
|
||||||
VERBOSE="true"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Usage: $0 [options]"
|
|
||||||
echo "Options:"
|
|
||||||
echo " --heavynode Only loads index templates specific to heavynodes"
|
|
||||||
echo " --force Force reload all templates regardless of statefiles (default: false)"
|
|
||||||
echo " --verbose Enable verbose output"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
shift
|
|
||||||
done
|
|
||||||
|
|
||||||
load_template() {
|
|
||||||
local uri="$1"
|
|
||||||
local file="$2"
|
|
||||||
|
|
||||||
echo "Loading template file $file"
|
|
||||||
if ! output=$(retry 3 3 "so-elasticsearch-query $uri -d@$file -XPUT" "{\"acknowledged\":true}"); then
|
|
||||||
echo "$output"
|
|
||||||
|
|
||||||
return 1
|
|
||||||
|
|
||||||
elif [[ "$VERBOSE" == "true" ]]; then
|
|
||||||
echo "$output"
|
|
||||||
fi
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
check_required_component_template_exists() {
|
|
||||||
local required
|
|
||||||
local missing
|
|
||||||
local file=$1
|
|
||||||
|
|
||||||
required=$(jq '[((.composed_of //[]) - (.ignore_missing_component_templates // []))[]]' "$file")
|
|
||||||
missing=$(jq -n --argjson required "$required" --argjson component_templates "$component_templates" '(($required) - ($component_templates))')
|
|
||||||
|
|
||||||
if [[ $(jq length <<<"$missing") -gt 0 ]]; then
|
|
||||||
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
check_heavynode_compatiable_index_template() {
|
|
||||||
# The only templates that are relevant to heavynodes are from datasets defined in elasticagent/files/elastic-agent.yml.jinja.
|
|
||||||
# Heavynodes do not have fleet server packages installed and do not support elastic agents reporting directly to them.
|
|
||||||
local -A heavynode_index_templates=(
|
|
||||||
["so-import"]=1
|
|
||||||
["so-syslog"]=1
|
|
||||||
["so-logs-soc"]=1
|
|
||||||
["so-suricata"]=1
|
|
||||||
["so-suricata.alerts"]=1
|
|
||||||
["so-zeek"]=1
|
|
||||||
["so-strelka"]=1
|
|
||||||
)
|
|
||||||
|
|
||||||
local template_name="$1"
|
|
||||||
|
|
||||||
if [[ ! -v heavynode_index_templates["$template_name"] ]]; then
|
|
||||||
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
load_component_templates() {
|
|
||||||
local printed_name="$1"
|
|
||||||
local pattern="${ELASTICSEARCH_TEMPLATES_DIR}/component/$2"
|
|
||||||
local append_mappings="${3:-"false"}"
|
|
||||||
|
|
||||||
echo -e "\nLoading $printed_name component templates...\n"
|
|
||||||
|
|
||||||
if ! compgen -G "${pattern}/*.json" > /dev/null; then
|
|
||||||
echo "No $printed_name component templates found in ${pattern}, skipping."
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
for component in "$pattern"/*.json; do
|
|
||||||
tmpl_name=$(basename "${component%.json}")
|
|
||||||
|
|
||||||
if [[ "$append_mappings" == "true" ]]; then
|
|
||||||
# avoid duplicating "-mappings" if it already exists in the component template filename
|
|
||||||
tmpl_name="${tmpl_name%-mappings}-mappings"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! load_template "_component_template/${tmpl_name}" "$component"; then
|
|
||||||
SO_LOAD_FAILURES=$((SO_LOAD_FAILURES + 1))
|
|
||||||
SO_LOAD_FAILURES_NAMES+=("$component")
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
check_elasticsearch_responsive() {
|
|
||||||
# Cannot load templates if Elasticsearch is not responding.
|
|
||||||
# NOTE: Slightly faster exit w/ failure than previous "retry 240 1" if there is a problem with Elasticsearch the
|
|
||||||
# script should exit sooner rather than hang at the 'so-elasticsearch-templates' salt state.
|
|
||||||
retry 3 15 "so-elasticsearch-query / --output /dev/null --fail" ||
|
|
||||||
fail "Elasticsearch is not responding. Please review Elasticsearch logs /opt/so/log/elasticsearch/securityonion.log for more details. Additionally, consider running so-elasticsearch-troubleshoot."
|
|
||||||
}
|
|
||||||
|
|
||||||
index_templates_exist() {
|
|
||||||
local templates_dir="$1"
|
|
||||||
|
|
||||||
if [[ ! -d "$templates_dir" ]]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
compgen -G "${templates_dir}/*.json" > /dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
should_load_addon_templates() {
|
|
||||||
if [[ "$IS_HEAVYNODE" == "true" ]]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Skip statefile checks when forcing template load
|
|
||||||
if [[ "$FORCE" != "true" ]]; then
|
|
||||||
if [[ ! -f "$SO_STATEFILE_SUCCESS" || -f "$ADDON_STATEFILE_SUCCESS" ]]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
index_templates_exist "$ADDON_TEMPLATES_DIR"
|
|
||||||
}
|
|
||||||
|
|
||||||
if [[ "$FORCE" == "true" || ! -f "$SO_STATEFILE_SUCCESS" ]] && index_templates_exist "$SO_TEMPLATES_DIR"; then
|
|
||||||
check_elasticsearch_responsive
|
|
||||||
|
|
||||||
if [[ "$IS_HEAVYNODE" == "false" ]]; then
|
|
||||||
# TODO: Better way to check if fleet server is installed vs checking for Elastic Defend component template.
|
|
||||||
fleet_check="logs-endpoint.alerts@package"
|
|
||||||
if ! so-elasticsearch-query "_component_template/$fleet_check" --output /dev/null --retry 5 --retry-delay 3 --fail; then
|
|
||||||
# This check prevents so-elasticsearch-templates-load from running before so-elastic-fleet-setup has run.
|
|
||||||
echo -e "\nPackage $fleet_check not yet installed. Fleet Server may not be fully configured yet."
|
|
||||||
# Fleet Server is required because some SO index templates depend on components installed via
|
|
||||||
# specific integrations eg Elastic Defend. These are components that we do not manually create / manage
|
|
||||||
# via /opt/so/saltstack/salt/elasticsearch/templates/component/
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# load_component_templates "Name" "directory" "append '-mappings'?"
|
|
||||||
load_component_templates "ECS" "ecs" "true"
|
|
||||||
load_component_templates "Elastic Agent" "elastic-agent"
|
|
||||||
load_component_templates "Security Onion" "so"
|
|
||||||
|
|
||||||
component_templates=$(so-elasticsearch-component-templates-list)
|
|
||||||
echo -e "Loading Security Onion index templates...\n"
|
|
||||||
for so_idx_tmpl in "${SO_TEMPLATES_DIR}"/*.json; do
|
|
||||||
tmpl_name=$(basename "${so_idx_tmpl%-template.json}")
|
|
||||||
|
|
||||||
if [[ "$IS_HEAVYNODE" == "true" ]]; then
|
|
||||||
# TODO: Better way to load only heavynode specific templates
|
|
||||||
if ! check_heavynode_compatiable_index_template "$tmpl_name"; then
|
|
||||||
if [[ "$VERBOSE" == "true" ]]; then
|
|
||||||
echo "Skipping over $so_idx_tmpl, template is not a heavynode specific index template."
|
|
||||||
fi
|
|
||||||
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if check_required_component_template_exists "$so_idx_tmpl"; then
|
|
||||||
if ! load_template "_index_template/$tmpl_name" "$so_idx_tmpl"; then
|
|
||||||
SO_LOAD_FAILURES=$((SO_LOAD_FAILURES + 1))
|
|
||||||
SO_LOAD_FAILURES_NAMES+=("$so_idx_tmpl")
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "Skipping over $so_idx_tmpl due to missing required component template(s)."
|
|
||||||
SO_LOAD_FAILURES=$((SO_LOAD_FAILURES + 1))
|
|
||||||
SO_LOAD_FAILURES_NAMES+=("$so_idx_tmpl")
|
|
||||||
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ $SO_LOAD_FAILURES -eq 0 ]]; then
|
|
||||||
echo "All Security Onion core templates loaded successfully."
|
|
||||||
|
|
||||||
touch "$SO_STATEFILE_SUCCESS"
|
|
||||||
else
|
|
||||||
echo "Encountered $SO_LOAD_FAILURES failure(s) loading templates:"
|
|
||||||
for failed_template in "${SO_LOAD_FAILURES_NAMES[@]}"; do
|
|
||||||
echo " - $failed_template"
|
|
||||||
done
|
|
||||||
if [[ "$SHOULD_EXIT_ON_FAILURE" == "true" ]]; then
|
|
||||||
fail "Failed to load all Security Onion core templates successfully."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
elif ! index_templates_exist "$SO_TEMPLATES_DIR"; then
|
|
||||||
echo "No Security Onion core index templates found in ${SO_TEMPLATES_DIR}, skipping."
|
|
||||||
elif [[ -f "$SO_STATEFILE_SUCCESS" ]]; then
|
|
||||||
echo "Security Onion core templates already loaded"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Start loading addon templates
|
|
||||||
if should_load_addon_templates; then
|
|
||||||
|
|
||||||
check_elasticsearch_responsive
|
|
||||||
|
|
||||||
echo -e "\nLoading addon integration index templates...\n"
|
|
||||||
component_templates=$(so-elasticsearch-component-templates-list)
|
|
||||||
|
|
||||||
for addon_idx_tmpl in "${ADDON_TEMPLATES_DIR}"/*.json; do
|
|
||||||
tmpl_name=$(basename "${addon_idx_tmpl%-template.json}")
|
|
||||||
|
|
||||||
if check_required_component_template_exists "$addon_idx_tmpl"; then
|
|
||||||
if ! load_template "_index_template/${tmpl_name}" "$addon_idx_tmpl"; then
|
|
||||||
ADDON_LOAD_FAILURES=$((ADDON_LOAD_FAILURES + 1))
|
|
||||||
ADDON_LOAD_FAILURES_NAMES+=("$addon_idx_tmpl")
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "Skipping over $addon_idx_tmpl due to missing required component template(s)."
|
|
||||||
ADDON_LOAD_FAILURES=$((ADDON_LOAD_FAILURES + 1))
|
|
||||||
ADDON_LOAD_FAILURES_NAMES+=("$addon_idx_tmpl")
|
|
||||||
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ $ADDON_LOAD_FAILURES -eq 0 ]]; then
|
|
||||||
echo "All addon integration templates loaded successfully."
|
|
||||||
|
|
||||||
touch "$ADDON_STATEFILE_SUCCESS"
|
|
||||||
else
|
|
||||||
echo "Encountered $ADDON_LOAD_FAILURES failure(s) loading addon integration templates:"
|
|
||||||
for failed_template in "${ADDON_LOAD_FAILURES_NAMES[@]}"; do
|
|
||||||
echo " - $failed_template"
|
|
||||||
done
|
|
||||||
if [[ "$SHOULD_EXIT_ON_FAILURE" == "true" ]]; then
|
|
||||||
fail "Failed to load all addon integration templates successfully."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
elif [[ ! -f "$SO_STATEFILE_SUCCESS" && "$IS_HEAVYNODE" == "false" ]]; then
|
|
||||||
echo "Skipping loading addon integration templates until Security Onion core templates have been loaded."
|
|
||||||
|
|
||||||
elif [[ -f "$ADDON_STATEFILE_SUCCESS" && "$IS_HEAVYNODE" == "false" && "$FORCE" == "false" ]]; then
|
|
||||||
echo "Addon integration templates already loaded"
|
|
||||||
fi
|
|
||||||
@@ -6,65 +6,30 @@
|
|||||||
|
|
||||||
. /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" %}
|
|
||||||
{%- from 'elasticsearch/template.map.jinja' import ALL_ADDON_SETTINGS %}
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
{%- 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' %}
|
||||||
throttle
|
echo
|
||||||
put_policy "so-logs-detections.alerts-so" "{{ index }}-so" '{ "policy": {{ settings.policy | tojson(true) }} }' &
|
echo "Setting up so-logs-detections.alerts-so policy..."
|
||||||
|
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' %}
|
||||||
throttle
|
echo
|
||||||
put_policy "so-soc-logs" "so-soc-logs" '{ "policy": {{ settings.policy | tojson(true) }} }' &
|
echo "Setting up so-soc-logs policy..."
|
||||||
throttle
|
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) }} }'
|
||||||
put_policy "{{ index }}-logs" "{{ index }}-logs" '{ "policy": {{ settings.policy | tojson(true) }} }' &
|
echo
|
||||||
|
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 %}
|
||||||
throttle
|
echo
|
||||||
put_policy "{{ index }}-logs" "{{ index }}-logs" '{ "policy": {{ settings.policy | tojson(true) }} }' &
|
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
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
{%- if GLOBALS.role != "so-heavynode" %}
|
echo
|
||||||
{%- for index, settings in ALL_ADDON_SETTINGS.items() %}
|
|
||||||
{%- if settings.policy is defined %}
|
|
||||||
throttle
|
|
||||||
put_policy "{{ index }}-logs" "{{ index }}-logs" '{ "policy": {{ settings.policy | tojson(true) }} }' &
|
|
||||||
{%- endif %}
|
|
||||||
{%- endfor %}
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
wait
|
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
#!/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.
|
||||||
|
{%- import_yaml 'elasticfleet/defaults.yaml' as ELASTICFLEETDEFAULTS %}
|
||||||
|
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
||||||
|
|
||||||
|
STATE_FILE_INITIAL=/opt/so/state/estemplates_initial_load_attempt.txt
|
||||||
|
STATE_FILE_SUCCESS=/opt/so/state/estemplates.txt
|
||||||
|
|
||||||
|
if [[ -f $STATE_FILE_INITIAL ]]; then
|
||||||
|
# The initial template load has already run. As this is a subsequent load, all dependencies should
|
||||||
|
# already be satisified. Therefore, immediately exit/abort this script upon any template load failure
|
||||||
|
# since this is an unrecoverable failure.
|
||||||
|
should_exit_on_failure=1
|
||||||
|
else
|
||||||
|
# This is the initial template load, and there likely are some components not yet setup in Elasticsearch.
|
||||||
|
# Therefore load as many templates as possible at this time and if an error occurs proceed to the next
|
||||||
|
# template. But if at least one template fails to load do not mark the templates as having been loaded.
|
||||||
|
# This will allow the next load to resume the load of the templates that failed to load initially.
|
||||||
|
should_exit_on_failure=0
|
||||||
|
echo "This is the initial template load"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If soup is running, ignore errors
|
||||||
|
pgrep soup > /dev/null && should_exit_on_failure=0
|
||||||
|
|
||||||
|
load_failures=0
|
||||||
|
|
||||||
|
load_template() {
|
||||||
|
uri=$1
|
||||||
|
file=$2
|
||||||
|
|
||||||
|
echo "Loading template file $i"
|
||||||
|
if ! retry 3 1 "so-elasticsearch-query $uri -d@$file -XPUT" "{\"acknowledged\":true}"; then
|
||||||
|
if [[ $should_exit_on_failure -eq 1 ]]; then
|
||||||
|
fail "Could not load template file: $file"
|
||||||
|
else
|
||||||
|
load_failures=$((load_failures+1))
|
||||||
|
echo "Incremented load failure counter: $load_failures"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ ! -f $STATE_FILE_SUCCESS ]; then
|
||||||
|
echo "State file $STATE_FILE_SUCCESS not found. Running so-elasticsearch-templates-load."
|
||||||
|
|
||||||
|
. /usr/sbin/so-common
|
||||||
|
|
||||||
|
{% if GLOBALS.role != 'so-heavynode' %}
|
||||||
|
if [ -f /usr/sbin/so-elastic-fleet-common ]; then
|
||||||
|
. /usr/sbin/so-elastic-fleet-common
|
||||||
|
fi
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
default_conf_dir=/opt/so/conf
|
||||||
|
|
||||||
|
# Define a default directory to load pipelines from
|
||||||
|
ELASTICSEARCH_TEMPLATES="$default_conf_dir/elasticsearch/templates/"
|
||||||
|
|
||||||
|
{% if GLOBALS.role == 'so-heavynode' %}
|
||||||
|
file="/opt/so/conf/elasticsearch/templates/index/so-common-template.json"
|
||||||
|
{% else %}
|
||||||
|
file="/usr/sbin/so-elastic-fleet-common"
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
# Wait for ElasticSearch to initialize
|
||||||
|
echo -n "Waiting for ElasticSearch..."
|
||||||
|
retry 240 1 "so-elasticsearch-query / -k --output /dev/null --silent --head --fail" || fail "Connection attempt timed out. Unable to connect to ElasticSearch. \nPlease try: \n -checking log(s) in /var/log/elasticsearch/\n -running 'sudo docker ps' \n -running 'sudo so-elastic-restart'"
|
||||||
|
{% if GLOBALS.role != 'so-heavynode' %}
|
||||||
|
TEMPLATE="logs-endpoint.alerts@package"
|
||||||
|
INSTALLED=$(so-elasticsearch-query _component_template/$TEMPLATE | jq -r .component_templates[0].name)
|
||||||
|
if [ "$INSTALLED" != "$TEMPLATE" ]; then
|
||||||
|
echo
|
||||||
|
echo "Packages not yet installed."
|
||||||
|
echo
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
touch $STATE_FILE_INITIAL
|
||||||
|
|
||||||
|
cd ${ELASTICSEARCH_TEMPLATES}/component/ecs
|
||||||
|
|
||||||
|
echo "Loading ECS component templates..."
|
||||||
|
for i in *; do
|
||||||
|
TEMPLATE=$(echo $i | cut -d '.' -f1)
|
||||||
|
load_template "_component_template/${TEMPLATE}-mappings" "$i"
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
|
||||||
|
cd ${ELASTICSEARCH_TEMPLATES}/component/elastic-agent
|
||||||
|
|
||||||
|
echo "Loading Elastic Agent component templates..."
|
||||||
|
{% if GLOBALS.role == 'so-heavynode' %}
|
||||||
|
component_pattern="so-*"
|
||||||
|
{% else %}
|
||||||
|
component_pattern="*"
|
||||||
|
{% endif %}
|
||||||
|
for i in $component_pattern; do
|
||||||
|
TEMPLATE=${i::-5}
|
||||||
|
load_template "_component_template/$TEMPLATE" "$i"
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Load SO-specific component templates
|
||||||
|
cd ${ELASTICSEARCH_TEMPLATES}/component/so
|
||||||
|
|
||||||
|
echo "Loading Security Onion component templates..."
|
||||||
|
for i in *; do
|
||||||
|
TEMPLATE=$(echo $i | cut -d '.' -f1);
|
||||||
|
load_template "_component_template/$TEMPLATE" "$i"
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Load SO index templates
|
||||||
|
cd ${ELASTICSEARCH_TEMPLATES}/index
|
||||||
|
|
||||||
|
echo "Loading Security Onion index templates..."
|
||||||
|
shopt -s extglob
|
||||||
|
{% if GLOBALS.role == 'so-heavynode' %}
|
||||||
|
pattern="!(*1password*|*aws*|*azure*|*cloudflare*|*elastic_agent*|*fim*|*github*|*google*|*osquery*|*system*|*windows*|*endpoint*|*elasticsearch*|*generic*|*fleet_server*|*soc*)"
|
||||||
|
{% else %}
|
||||||
|
pattern="*"
|
||||||
|
{% endif %}
|
||||||
|
# Index templates will be skipped if the following conditions are met:
|
||||||
|
# 1. The template is part of the "so-logs-" template group
|
||||||
|
# 2. The template name does not correlate to at least one existing component template
|
||||||
|
# In this situation, the script will treat the skipped template as a temporary failure
|
||||||
|
# and allow the templates to be loaded again on the next run or highstate, whichever
|
||||||
|
# comes first.
|
||||||
|
COMPONENT_LIST=$(so-elasticsearch-component-templates-list)
|
||||||
|
for i in $pattern; do
|
||||||
|
TEMPLATE=${i::-14}
|
||||||
|
COMPONENT_PATTERN=${TEMPLATE:3}
|
||||||
|
MATCH=$(echo "$TEMPLATE" | grep -E "^so-logs-|^so-metrics" | grep -vE "detections|osquery")
|
||||||
|
if [[ -n "$MATCH" && ! "$COMPONENT_LIST" =~ "$COMPONENT_PATTERN" && ! "$COMPONENT_PATTERN" =~ \.generic|logs-winlog\.winlog ]]; then
|
||||||
|
load_failures=$((load_failures+1))
|
||||||
|
echo "Component template does not exist for $COMPONENT_PATTERN. The index template will not be loaded. Load failures: $load_failures"
|
||||||
|
else
|
||||||
|
load_template "_index_template/$TEMPLATE" "$i"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
{% if GLOBALS.role == 'so-heavynode' %}
|
||||||
|
echo "Common template does not exist. Exiting..."
|
||||||
|
{% else %}
|
||||||
|
echo "Elastic Fleet not configured. Exiting..."
|
||||||
|
{% endif %}
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd - >/dev/null
|
||||||
|
|
||||||
|
if [[ $load_failures -eq 0 ]]; then
|
||||||
|
echo "All templates loaded successfully"
|
||||||
|
touch $STATE_FILE_SUCCESS
|
||||||
|
else
|
||||||
|
echo "Encountered $load_failures templates that were unable to load, likely due to missing dependencies that will be available later; will retry on next highstate"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Templates already loaded"
|
||||||
|
fi
|
||||||
@@ -11,15 +11,8 @@
|
|||||||
'so-kratos',
|
'so-kratos',
|
||||||
'so-hydra',
|
'so-hydra',
|
||||||
'so-nginx',
|
'so-nginx',
|
||||||
'so-postgres',
|
|
||||||
'so-redis',
|
'so-redis',
|
||||||
'so-soc',
|
'so-soc',
|
||||||
'so-strelka-coordinator',
|
|
||||||
'so-strelka-gatekeeper',
|
|
||||||
'so-strelka-frontend',
|
|
||||||
'so-strelka-backend',
|
|
||||||
'so-strelka-manager',
|
|
||||||
'so-strelka-filestream'
|
|
||||||
] %}
|
] %}
|
||||||
|
|
||||||
{% elif GLOBALS.role in ['so-manager', 'so-standalone','so-managersearch', 'so-managerhype'] %}
|
{% elif GLOBALS.role in ['so-manager', 'so-standalone','so-managersearch', 'so-managerhype'] %}
|
||||||
@@ -35,15 +28,8 @@
|
|||||||
'so-hydra',
|
'so-hydra',
|
||||||
'so-logstash',
|
'so-logstash',
|
||||||
'so-nginx',
|
'so-nginx',
|
||||||
'so-postgres',
|
|
||||||
'so-redis',
|
'so-redis',
|
||||||
'so-soc',
|
'so-soc',
|
||||||
'so-strelka-coordinator',
|
|
||||||
'so-strelka-gatekeeper',
|
|
||||||
'so-strelka-frontend',
|
|
||||||
'so-strelka-backend',
|
|
||||||
'so-strelka-manager',
|
|
||||||
'so-strelka-filestream'
|
|
||||||
] %}
|
] %}
|
||||||
|
|
||||||
{% elif GLOBALS.role == 'so-searchnode' %}
|
{% elif GLOBALS.role == 'so-searchnode' %}
|
||||||
@@ -60,12 +46,6 @@
|
|||||||
'so-logstash',
|
'so-logstash',
|
||||||
'so-nginx',
|
'so-nginx',
|
||||||
'so-redis',
|
'so-redis',
|
||||||
'so-strelka-coordinator',
|
|
||||||
'so-strelka-gatekeeper',
|
|
||||||
'so-strelka-frontend',
|
|
||||||
'so-strelka-backend',
|
|
||||||
'so-strelka-manager',
|
|
||||||
'so-strelka-filestream'
|
|
||||||
] %}
|
] %}
|
||||||
|
|
||||||
{% elif GLOBALS.role == 'so-import' %}
|
{% elif GLOBALS.role == 'so-import' %}
|
||||||
@@ -79,7 +59,6 @@
|
|||||||
'so-kratos',
|
'so-kratos',
|
||||||
'so-hydra',
|
'so-hydra',
|
||||||
'so-nginx',
|
'so-nginx',
|
||||||
'so-postgres',
|
|
||||||
'so-soc'
|
'so-soc'
|
||||||
] %}
|
] %}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ firewall:
|
|||||||
self: []
|
self: []
|
||||||
sensor: []
|
sensor: []
|
||||||
standalone: []
|
standalone: []
|
||||||
strelka_frontend: []
|
|
||||||
syslog: []
|
syslog: []
|
||||||
desktop: []
|
desktop: []
|
||||||
customhostgroup0: []
|
customhostgroup0: []
|
||||||
@@ -98,10 +97,6 @@ firewall:
|
|||||||
tcp:
|
tcp:
|
||||||
- 8086
|
- 8086
|
||||||
udp: []
|
udp: []
|
||||||
postgres:
|
|
||||||
tcp:
|
|
||||||
- 5432
|
|
||||||
udp: []
|
|
||||||
kafka_controller:
|
kafka_controller:
|
||||||
tcp:
|
tcp:
|
||||||
- 9093
|
- 9093
|
||||||
@@ -144,10 +139,6 @@ firewall:
|
|||||||
tcp:
|
tcp:
|
||||||
- 22
|
- 22
|
||||||
udp: []
|
udp: []
|
||||||
strelka_frontend:
|
|
||||||
tcp:
|
|
||||||
- 57314
|
|
||||||
udp: []
|
|
||||||
syslog:
|
syslog:
|
||||||
tcp:
|
tcp:
|
||||||
- 514
|
- 514
|
||||||
@@ -197,7 +188,6 @@ firewall:
|
|||||||
- kibana
|
- kibana
|
||||||
- redis
|
- redis
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- elasticsearch_rest
|
- elasticsearch_rest
|
||||||
- elasticsearch_node
|
- elasticsearch_node
|
||||||
- localrules
|
- localrules
|
||||||
@@ -227,9 +217,6 @@ firewall:
|
|||||||
- elastic_agent_control
|
- elastic_agent_control
|
||||||
- elastic_agent_data
|
- elastic_agent_data
|
||||||
- elastic_agent_update
|
- elastic_agent_update
|
||||||
strelka_frontend:
|
|
||||||
portgroups:
|
|
||||||
- strelka_frontend
|
|
||||||
analyst:
|
analyst:
|
||||||
portgroups:
|
portgroups:
|
||||||
- nginx
|
- nginx
|
||||||
@@ -384,7 +371,6 @@ firewall:
|
|||||||
- kibana
|
- kibana
|
||||||
- redis
|
- redis
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- elasticsearch_rest
|
- elasticsearch_rest
|
||||||
- elasticsearch_node
|
- elasticsearch_node
|
||||||
- docker_registry
|
- docker_registry
|
||||||
@@ -398,7 +384,6 @@ firewall:
|
|||||||
- elasticsearch_rest
|
- elasticsearch_rest
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- sensoroni
|
- sensoroni
|
||||||
- yum
|
- yum
|
||||||
- beats_5044
|
- beats_5044
|
||||||
@@ -411,7 +396,6 @@ firewall:
|
|||||||
portgroups:
|
portgroups:
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- sensoroni
|
- sensoroni
|
||||||
- yum
|
- yum
|
||||||
- beats_5044
|
- beats_5044
|
||||||
@@ -429,7 +413,6 @@ firewall:
|
|||||||
- yum
|
- yum
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- sensoroni
|
- sensoroni
|
||||||
searchnode:
|
searchnode:
|
||||||
portgroups:
|
portgroups:
|
||||||
@@ -440,7 +423,6 @@ firewall:
|
|||||||
- yum
|
- yum
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- elastic_agent_control
|
- elastic_agent_control
|
||||||
- elastic_agent_data
|
- elastic_agent_data
|
||||||
- elastic_agent_update
|
- elastic_agent_update
|
||||||
@@ -454,7 +436,6 @@ firewall:
|
|||||||
- yum
|
- yum
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- elastic_agent_control
|
- elastic_agent_control
|
||||||
- elastic_agent_data
|
- elastic_agent_data
|
||||||
- elastic_agent_update
|
- elastic_agent_update
|
||||||
@@ -464,7 +445,6 @@ firewall:
|
|||||||
- yum
|
- yum
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- elastic_agent_control
|
- elastic_agent_control
|
||||||
- elastic_agent_data
|
- elastic_agent_data
|
||||||
- elastic_agent_update
|
- elastic_agent_update
|
||||||
@@ -498,7 +478,6 @@ firewall:
|
|||||||
portgroups:
|
portgroups:
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- sensoroni
|
- sensoroni
|
||||||
- yum
|
- yum
|
||||||
- elastic_agent_control
|
- elastic_agent_control
|
||||||
@@ -509,7 +488,6 @@ firewall:
|
|||||||
- yum
|
- yum
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- elastic_agent_control
|
- elastic_agent_control
|
||||||
- elastic_agent_data
|
- elastic_agent_data
|
||||||
- elastic_agent_update
|
- elastic_agent_update
|
||||||
@@ -604,7 +582,6 @@ firewall:
|
|||||||
- kibana
|
- kibana
|
||||||
- redis
|
- redis
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- elasticsearch_rest
|
- elasticsearch_rest
|
||||||
- elasticsearch_node
|
- elasticsearch_node
|
||||||
- docker_registry
|
- docker_registry
|
||||||
@@ -618,7 +595,6 @@ firewall:
|
|||||||
- elasticsearch_rest
|
- elasticsearch_rest
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- sensoroni
|
- sensoroni
|
||||||
- yum
|
- yum
|
||||||
- beats_5044
|
- beats_5044
|
||||||
@@ -631,7 +607,6 @@ firewall:
|
|||||||
portgroups:
|
portgroups:
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- sensoroni
|
- sensoroni
|
||||||
- yum
|
- yum
|
||||||
- beats_5044
|
- beats_5044
|
||||||
@@ -649,7 +624,6 @@ firewall:
|
|||||||
- yum
|
- yum
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- sensoroni
|
- sensoroni
|
||||||
searchnode:
|
searchnode:
|
||||||
portgroups:
|
portgroups:
|
||||||
@@ -660,7 +634,6 @@ firewall:
|
|||||||
- yum
|
- yum
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- elastic_agent_control
|
- elastic_agent_control
|
||||||
- elastic_agent_data
|
- elastic_agent_data
|
||||||
- elastic_agent_update
|
- elastic_agent_update
|
||||||
@@ -674,7 +647,6 @@ firewall:
|
|||||||
- yum
|
- yum
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- elastic_agent_control
|
- elastic_agent_control
|
||||||
- elastic_agent_data
|
- elastic_agent_data
|
||||||
- elastic_agent_update
|
- elastic_agent_update
|
||||||
@@ -684,7 +656,6 @@ firewall:
|
|||||||
- yum
|
- yum
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- elastic_agent_control
|
- elastic_agent_control
|
||||||
- elastic_agent_data
|
- elastic_agent_data
|
||||||
- elastic_agent_update
|
- elastic_agent_update
|
||||||
@@ -716,7 +687,6 @@ firewall:
|
|||||||
portgroups:
|
portgroups:
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- sensoroni
|
- sensoroni
|
||||||
- yum
|
- yum
|
||||||
- elastic_agent_control
|
- elastic_agent_control
|
||||||
@@ -727,7 +697,6 @@ firewall:
|
|||||||
- yum
|
- yum
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- elastic_agent_control
|
- elastic_agent_control
|
||||||
- elastic_agent_data
|
- elastic_agent_data
|
||||||
- elastic_agent_update
|
- elastic_agent_update
|
||||||
@@ -822,7 +791,6 @@ firewall:
|
|||||||
- kibana
|
- kibana
|
||||||
- redis
|
- redis
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- elasticsearch_rest
|
- elasticsearch_rest
|
||||||
- elasticsearch_node
|
- elasticsearch_node
|
||||||
- docker_registry
|
- docker_registry
|
||||||
@@ -836,7 +804,6 @@ firewall:
|
|||||||
- elasticsearch_rest
|
- elasticsearch_rest
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- sensoroni
|
- sensoroni
|
||||||
- yum
|
- yum
|
||||||
- beats_5044
|
- beats_5044
|
||||||
@@ -849,7 +816,6 @@ firewall:
|
|||||||
portgroups:
|
portgroups:
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- sensoroni
|
- sensoroni
|
||||||
- yum
|
- yum
|
||||||
- beats_5044
|
- beats_5044
|
||||||
@@ -867,7 +833,6 @@ firewall:
|
|||||||
- yum
|
- yum
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- sensoroni
|
- sensoroni
|
||||||
searchnode:
|
searchnode:
|
||||||
portgroups:
|
portgroups:
|
||||||
@@ -877,7 +842,6 @@ firewall:
|
|||||||
- yum
|
- yum
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- elastic_agent_control
|
- elastic_agent_control
|
||||||
- elastic_agent_data
|
- elastic_agent_data
|
||||||
- elastic_agent_update
|
- elastic_agent_update
|
||||||
@@ -890,7 +854,6 @@ firewall:
|
|||||||
- yum
|
- yum
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- elastic_agent_control
|
- elastic_agent_control
|
||||||
- elastic_agent_data
|
- elastic_agent_data
|
||||||
- elastic_agent_update
|
- elastic_agent_update
|
||||||
@@ -900,7 +863,6 @@ firewall:
|
|||||||
- yum
|
- yum
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- elastic_agent_control
|
- elastic_agent_control
|
||||||
- elastic_agent_data
|
- elastic_agent_data
|
||||||
- elastic_agent_update
|
- elastic_agent_update
|
||||||
@@ -934,7 +896,6 @@ firewall:
|
|||||||
portgroups:
|
portgroups:
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- sensoroni
|
- sensoroni
|
||||||
- yum
|
- yum
|
||||||
- elastic_agent_control
|
- elastic_agent_control
|
||||||
@@ -945,7 +906,6 @@ firewall:
|
|||||||
- yum
|
- yum
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- elastic_agent_control
|
- elastic_agent_control
|
||||||
- elastic_agent_data
|
- elastic_agent_data
|
||||||
- elastic_agent_update
|
- elastic_agent_update
|
||||||
@@ -1043,7 +1003,6 @@ firewall:
|
|||||||
- kibana
|
- kibana
|
||||||
- redis
|
- redis
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- elasticsearch_rest
|
- elasticsearch_rest
|
||||||
- elasticsearch_node
|
- elasticsearch_node
|
||||||
- docker_registry
|
- docker_registry
|
||||||
@@ -1057,14 +1016,12 @@ firewall:
|
|||||||
- elastic_agent_data
|
- elastic_agent_data
|
||||||
- elastic_agent_update
|
- elastic_agent_update
|
||||||
- endgame
|
- endgame
|
||||||
- strelka_frontend
|
|
||||||
- localrules
|
- localrules
|
||||||
fleet:
|
fleet:
|
||||||
portgroups:
|
portgroups:
|
||||||
- elasticsearch_rest
|
- elasticsearch_rest
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- sensoroni
|
- sensoroni
|
||||||
- yum
|
- yum
|
||||||
- beats_5044
|
- beats_5044
|
||||||
@@ -1077,7 +1034,6 @@ firewall:
|
|||||||
portgroups:
|
portgroups:
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- sensoroni
|
- sensoroni
|
||||||
- yum
|
- yum
|
||||||
- beats_5044
|
- beats_5044
|
||||||
@@ -1089,7 +1045,6 @@ firewall:
|
|||||||
portgroups:
|
portgroups:
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- sensoroni
|
- sensoroni
|
||||||
- yum
|
- yum
|
||||||
- beats_5044
|
- beats_5044
|
||||||
@@ -1101,7 +1056,6 @@ firewall:
|
|||||||
portgroups:
|
portgroups:
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- sensoroni
|
- sensoroni
|
||||||
- yum
|
- yum
|
||||||
- redis
|
- redis
|
||||||
@@ -1111,7 +1065,6 @@ firewall:
|
|||||||
portgroups:
|
portgroups:
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- sensoroni
|
- sensoroni
|
||||||
- yum
|
- yum
|
||||||
- redis
|
- redis
|
||||||
@@ -1122,7 +1075,6 @@ firewall:
|
|||||||
- yum
|
- yum
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- elastic_agent_control
|
- elastic_agent_control
|
||||||
- elastic_agent_data
|
- elastic_agent_data
|
||||||
- elastic_agent_update
|
- elastic_agent_update
|
||||||
@@ -1152,14 +1104,10 @@ firewall:
|
|||||||
- external_suricata
|
- external_suricata
|
||||||
external_kafka:
|
external_kafka:
|
||||||
portgroups: []
|
portgroups: []
|
||||||
strelka_frontend:
|
|
||||||
portgroups:
|
|
||||||
- strelka_frontend
|
|
||||||
desktop:
|
desktop:
|
||||||
portgroups:
|
portgroups:
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- sensoroni
|
- sensoroni
|
||||||
- yum
|
- yum
|
||||||
- elastic_agent_control
|
- elastic_agent_control
|
||||||
@@ -1170,7 +1118,6 @@ firewall:
|
|||||||
- yum
|
- yum
|
||||||
- docker_registry
|
- docker_registry
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- elastic_agent_control
|
- elastic_agent_control
|
||||||
- elastic_agent_data
|
- elastic_agent_data
|
||||||
- elastic_agent_update
|
- elastic_agent_update
|
||||||
@@ -1350,9 +1297,6 @@ firewall:
|
|||||||
chain:
|
chain:
|
||||||
DOCKER-USER:
|
DOCKER-USER:
|
||||||
hostgroups:
|
hostgroups:
|
||||||
strelka_frontend:
|
|
||||||
portgroups:
|
|
||||||
- strelka_frontend
|
|
||||||
customhostgroup0:
|
customhostgroup0:
|
||||||
portgroups: []
|
portgroups: []
|
||||||
customhostgroup1:
|
customhostgroup1:
|
||||||
@@ -1442,9 +1386,6 @@ firewall:
|
|||||||
- syslog
|
- syslog
|
||||||
- elasticsearch_node
|
- elasticsearch_node
|
||||||
- elasticsearch_rest
|
- elasticsearch_rest
|
||||||
strelka_frontend:
|
|
||||||
portgroups:
|
|
||||||
- strelka_frontend
|
|
||||||
syslog:
|
syslog:
|
||||||
portgroups:
|
portgroups:
|
||||||
- syslog
|
- syslog
|
||||||
@@ -1514,7 +1455,6 @@ firewall:
|
|||||||
- kibana
|
- kibana
|
||||||
- redis
|
- redis
|
||||||
- influxdb
|
- influxdb
|
||||||
- postgres
|
|
||||||
- elasticsearch_rest
|
- elasticsearch_rest
|
||||||
- elasticsearch_node
|
- elasticsearch_node
|
||||||
- elastic_agent_control
|
- elastic_agent_control
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ firewall:
|
|||||||
self: *ROhostgroupsettingsadv
|
self: *ROhostgroupsettingsadv
|
||||||
sensor: *hostgroupsettings
|
sensor: *hostgroupsettings
|
||||||
standalone: *hostgroupsettings
|
standalone: *hostgroupsettings
|
||||||
strelka_frontend: *hostgroupsettings
|
|
||||||
syslog: *hostgroupsettings
|
syslog: *hostgroupsettings
|
||||||
desktop: *hostgroupsettings
|
desktop: *hostgroupsettings
|
||||||
customhostgroup0: &customhostgroupsettings
|
customhostgroup0: &customhostgroupsettings
|
||||||
@@ -156,9 +155,6 @@ firewall:
|
|||||||
ssh:
|
ssh:
|
||||||
tcp: *tcpsettings
|
tcp: *tcpsettings
|
||||||
udp: *udpsettings
|
udp: *udpsettings
|
||||||
strelka_frontend:
|
|
||||||
tcp: *tcpsettings
|
|
||||||
udp: *udpsettings
|
|
||||||
syslog:
|
syslog:
|
||||||
tcp: *tcpsettings
|
tcp: *tcpsettings
|
||||||
udp: *udpsettings
|
udp: *udpsettings
|
||||||
@@ -224,9 +220,7 @@ firewall:
|
|||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
elastic_agent_endpoint:
|
elastic_agent_endpoint:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
external_suricata:
|
external_suricata:
|
||||||
portgroups: *portgroupsdocker
|
|
||||||
strelka_frontend:
|
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
syslog:
|
syslog:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
@@ -236,24 +230,24 @@ firewall:
|
|||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
customhostgroup0:
|
customhostgroup0:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
customhostgroup1:
|
customhostgroup1:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
customhostgroup2:
|
customhostgroup2:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
customhostgroup3:
|
customhostgroup3:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
customhostgroup4:
|
customhostgroup4:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
customhostgroup5:
|
customhostgroup5:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
customhostgroup6:
|
customhostgroup6:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
customhostgroup7:
|
customhostgroup7:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
customhostgroup8:
|
customhostgroup8:
|
||||||
|
portgroups: *portgroupsdocker
|
||||||
|
customhostgroup9:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
customhostgroup9:
|
|
||||||
portgroups: *portgroupsdocker
|
|
||||||
INPUT:
|
INPUT:
|
||||||
hostgroups:
|
hostgroups:
|
||||||
anywhere:
|
anywhere:
|
||||||
@@ -569,9 +563,7 @@ firewall:
|
|||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
endgame:
|
endgame:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
external_suricata:
|
external_suricata:
|
||||||
portgroups: *portgroupsdocker
|
|
||||||
strelka_frontend:
|
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
syslog:
|
syslog:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
@@ -711,28 +703,26 @@ firewall:
|
|||||||
hostgroups:
|
hostgroups:
|
||||||
self:
|
self:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
strelka_frontend:
|
|
||||||
portgroups: *portgroupsdocker
|
|
||||||
customhostgroup0:
|
customhostgroup0:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
customhostgroup1:
|
customhostgroup1:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
customhostgroup2:
|
customhostgroup2:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
customhostgroup3:
|
customhostgroup3:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
customhostgroup4:
|
customhostgroup4:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
customhostgroup5:
|
customhostgroup5:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
customhostgroup6:
|
customhostgroup6:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
customhostgroup7:
|
customhostgroup7:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
customhostgroup8:
|
customhostgroup8:
|
||||||
|
portgroups: *portgroupsdocker
|
||||||
|
customhostgroup9:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
customhostgroup9:
|
|
||||||
portgroups: *portgroupsdocker
|
|
||||||
INPUT:
|
INPUT:
|
||||||
hostgroups:
|
hostgroups:
|
||||||
anywhere:
|
anywhere:
|
||||||
@@ -743,23 +733,23 @@ firewall:
|
|||||||
portgroups: *portgroupshost
|
portgroups: *portgroupshost
|
||||||
customhostgroup0:
|
customhostgroup0:
|
||||||
portgroups: *portgroupshost
|
portgroups: *portgroupshost
|
||||||
customhostgroup1:
|
customhostgroup1:
|
||||||
portgroups: *portgroupshost
|
portgroups: *portgroupshost
|
||||||
customhostgroup2:
|
customhostgroup2:
|
||||||
portgroups: *portgroupshost
|
portgroups: *portgroupshost
|
||||||
customhostgroup3:
|
customhostgroup3:
|
||||||
portgroups: *portgroupshost
|
portgroups: *portgroupshost
|
||||||
customhostgroup4:
|
customhostgroup4:
|
||||||
portgroups: *portgroupshost
|
portgroups: *portgroupshost
|
||||||
customhostgroup5:
|
customhostgroup5:
|
||||||
portgroups: *portgroupshost
|
portgroups: *portgroupshost
|
||||||
customhostgroup6:
|
customhostgroup6:
|
||||||
portgroups: *portgroupshost
|
portgroups: *portgroupshost
|
||||||
customhostgroup7:
|
customhostgroup7:
|
||||||
portgroups: *portgroupshost
|
portgroups: *portgroupshost
|
||||||
customhostgroup8:
|
customhostgroup8:
|
||||||
portgroups: *portgroupshost
|
portgroups: *portgroupshost
|
||||||
customhostgroup9:
|
customhostgroup9:
|
||||||
portgroups: *portgroupshost
|
portgroups: *portgroupshost
|
||||||
|
|
||||||
heavynode:
|
heavynode:
|
||||||
@@ -774,11 +764,9 @@ firewall:
|
|||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
self:
|
self:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
strelka_frontend:
|
|
||||||
portgroups: *portgroupsdocker
|
|
||||||
customhostgroup0:
|
customhostgroup0:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
customhostgroup1:
|
customhostgroup1:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
customhostgroup2:
|
customhostgroup2:
|
||||||
portgroups: *portgroupsdocker
|
portgroups: *portgroupsdocker
|
||||||
|
|||||||
@@ -11,14 +11,18 @@ global:
|
|||||||
regexFailureMessage: You must enter a valid IP address or CIDR.
|
regexFailureMessage: You must enter a valid IP address or CIDR.
|
||||||
mdengine:
|
mdengine:
|
||||||
description: Which engine to use for meta data generation. Options are ZEEK and SURICATA.
|
description: Which engine to use for meta data generation. Options are ZEEK and SURICATA.
|
||||||
|
regex: ^(ZEEK|SURICATA)$
|
||||||
options:
|
options:
|
||||||
- ZEEK
|
- ZEEK
|
||||||
- SURICATA
|
- SURICATA
|
||||||
|
regexFailureMessage: You must enter either ZEEK or SURICATA.
|
||||||
global: True
|
global: True
|
||||||
pcapengine:
|
pcapengine:
|
||||||
description: Which engine to use for generating pcap. Currently only SURICATA is supported.
|
description: Which engine to use for generating pcap. Currently only SURICATA is supported.
|
||||||
|
regex: ^(SURICATA)$
|
||||||
options:
|
options:
|
||||||
- SURICATA
|
- SURICATA
|
||||||
|
regexFailureMessage: You must enter either SURICATA.
|
||||||
global: True
|
global: True
|
||||||
ids:
|
ids:
|
||||||
description: Which IDS engine to use. Currently only Suricata is supported.
|
description: Which IDS engine to use. Currently only Suricata is supported.
|
||||||
@@ -38,9 +42,11 @@ global:
|
|||||||
advanced: True
|
advanced: True
|
||||||
pipeline:
|
pipeline:
|
||||||
description: Sets which pipeline technology for events to use. The use of Kafka requires a Security Onion Pro license.
|
description: Sets which pipeline technology for events to use. The use of Kafka requires a Security Onion Pro license.
|
||||||
|
regex: ^(REDIS|KAFKA)$
|
||||||
options:
|
options:
|
||||||
- REDIS
|
- REDIS
|
||||||
- KAFKA
|
- KAFKA
|
||||||
|
regexFailureMessage: You must enter either REDIS or KAFKA.
|
||||||
global: True
|
global: True
|
||||||
advanced: True
|
advanced: True
|
||||||
repo_host:
|
repo_host:
|
||||||
|
|||||||
@@ -85,10 +85,7 @@ influxdb:
|
|||||||
description: The log level to use for outputting log statements. Allowed values are debug, info, or error.
|
description: The log level to use for outputting log statements. Allowed values are debug, info, or error.
|
||||||
global: True
|
global: True
|
||||||
advanced: false
|
advanced: false
|
||||||
options:
|
regex: ^(info|debug|error)$
|
||||||
- info
|
|
||||||
- debug
|
|
||||||
- error
|
|
||||||
helpLink: influxdb
|
helpLink: influxdb
|
||||||
metrics-disabled:
|
metrics-disabled:
|
||||||
description: If true, the HTTP endpoint that exposes internal InfluxDB metrics will be inaccessible.
|
description: If true, the HTTP endpoint that exposes internal InfluxDB metrics will be inaccessible.
|
||||||
@@ -143,9 +140,7 @@ influxdb:
|
|||||||
description: Determines the type of storage used for secrets. Allowed values are bolt or vault.
|
description: Determines the type of storage used for secrets. Allowed values are bolt or vault.
|
||||||
global: True
|
global: True
|
||||||
advanced: True
|
advanced: True
|
||||||
options:
|
regex: ^(bolt|vault)$
|
||||||
- bolt
|
|
||||||
- vault
|
|
||||||
helpLink: influxdb
|
helpLink: influxdb
|
||||||
session-length:
|
session-length:
|
||||||
description: Number of minutes that a user login session can remain authenticated.
|
description: Number of minutes that a user login session can remain authenticated.
|
||||||
@@ -265,9 +260,7 @@ influxdb:
|
|||||||
description: The type of data store to use for HTTP resources. Allowed values are disk or memory. Memory should not be used for production Security Onion installations.
|
description: The type of data store to use for HTTP resources. Allowed values are disk or memory. Memory should not be used for production Security Onion installations.
|
||||||
global: True
|
global: True
|
||||||
advanced: True
|
advanced: True
|
||||||
options:
|
regex: ^(disk|memory)$
|
||||||
- disk
|
|
||||||
- memory
|
|
||||||
helpLink: influxdb
|
helpLink: influxdb
|
||||||
tls-cert:
|
tls-cert:
|
||||||
description: The container path to the certificate to use for TLS encryption of the HTTP requests and responses.
|
description: The container path to the certificate to use for TLS encryption of the HTTP requests and responses.
|
||||||
|
|||||||
@@ -128,13 +128,10 @@ kafka:
|
|||||||
title: ssl.keystore.password
|
title: ssl.keystore.password
|
||||||
sensitive: True
|
sensitive: True
|
||||||
helpLink: kafka
|
helpLink: kafka
|
||||||
ssl_x_keystore_x_type:
|
ssl_x_keystore_x_type:
|
||||||
description: The key store file format.
|
description: The key store file format.
|
||||||
title: ssl.keystore.type
|
title: ssl.keystore.type
|
||||||
options:
|
regex: ^(JKS|PKCS12|PEM)$
|
||||||
- JKS
|
|
||||||
- PKCS12
|
|
||||||
- PEM
|
|
||||||
helpLink: kafka
|
helpLink: kafka
|
||||||
ssl_x_truststore_x_location:
|
ssl_x_truststore_x_location:
|
||||||
description: The trust store file location within the Docker container.
|
description: The trust store file location within the Docker container.
|
||||||
@@ -163,11 +160,7 @@ kafka:
|
|||||||
security_x_protocol:
|
security_x_protocol:
|
||||||
description: 'Broker communication protocol. Options are: SASL_SSL, PLAINTEXT, SSL, SASL_PLAINTEXT'
|
description: 'Broker communication protocol. Options are: SASL_SSL, PLAINTEXT, SSL, SASL_PLAINTEXT'
|
||||||
title: security.protocol
|
title: security.protocol
|
||||||
options:
|
regex: ^(SASL_SSL|PLAINTEXT|SSL|SASL_PLAINTEXT)
|
||||||
- SASL_SSL
|
|
||||||
- PLAINTEXT
|
|
||||||
- SSL
|
|
||||||
- SASL_PLAINTEXT
|
|
||||||
helpLink: kafka
|
helpLink: kafka
|
||||||
ssl_x_keystore_x_location:
|
ssl_x_keystore_x_location:
|
||||||
description: The key store file location within the Docker container.
|
description: The key store file location within the Docker container.
|
||||||
@@ -181,10 +174,7 @@ kafka:
|
|||||||
ssl_x_keystore_x_type:
|
ssl_x_keystore_x_type:
|
||||||
description: The key store file format.
|
description: The key store file format.
|
||||||
title: ssl.keystore.type
|
title: ssl.keystore.type
|
||||||
options:
|
regex: ^(JKS|PKCS12|PEM)$
|
||||||
- JKS
|
|
||||||
- PKCS12
|
|
||||||
- PEM
|
|
||||||
helpLink: kafka
|
helpLink: kafka
|
||||||
ssl_x_truststore_x_location:
|
ssl_x_truststore_x_location:
|
||||||
description: The trust store file location within the Docker container.
|
description: The trust store file location within the Docker container.
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ kibana:
|
|||||||
- default
|
- default
|
||||||
- file
|
- file
|
||||||
migrations:
|
migrations:
|
||||||
discardCorruptObjects: "9.3.3"
|
discardCorruptObjects: "8.18.8"
|
||||||
telemetry:
|
telemetry:
|
||||||
enabled: False
|
enabled: False
|
||||||
xpack:
|
xpack:
|
||||||
|
|||||||
@@ -9,5 +9,5 @@ SESSIONCOOKIE=$(curl -K /opt/so/conf/elasticsearch/curl.config -c - -X GET http:
|
|||||||
# Disable certain Features from showing up in the Kibana UI
|
# Disable certain Features from showing up in the Kibana UI
|
||||||
echo
|
echo
|
||||||
echo "Setting up default Kibana Space:"
|
echo "Setting up default Kibana Space:"
|
||||||
curl -K /opt/so/conf/elasticsearch/curl.config -b "sid=$SESSIONCOOKIE" -L -X PUT "localhost:5601/api/spaces/space/default" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d' {"id":"default","name":"Default","disabledFeatures":["ml","enterpriseSearch","logs","infrastructure","apm","uptime","monitoring","stackAlerts","actions","securitySolutionCasesV3","inventory","dataQuality","searchSynonyms","searchQueryRules","enterpriseSearchApplications","enterpriseSearchAnalytics","securitySolutionTimeline","securitySolutionNotes","securitySolutionRulesV1","entityManager","streams","cloudConnect","slo"]} ' >> /opt/so/log/kibana/misc.log
|
curl -K /opt/so/conf/elasticsearch/curl.config -b "sid=$SESSIONCOOKIE" -L -X PUT "localhost:5601/api/spaces/space/default" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d' {"id":"default","name":"Default","disabledFeatures":["ml","enterpriseSearch","logs","infrastructure","apm","uptime","monitoring","stackAlerts","actions","securitySolutionCasesV3","inventory","dataQuality","searchSynonyms","enterpriseSearchApplications","enterpriseSearchAnalytics","securitySolutionTimeline","securitySolutionNotes","entityManager"]} ' >> /opt/so/log/kibana/misc.log
|
||||||
echo
|
echo
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ kratos:
|
|||||||
description: Enables or disables the Kratos authentication system. WARNING - Disabling this process will cause the grid to malfunction. Re-enabling this setting will require manual effort via SSH.
|
description: Enables or disables the Kratos authentication system. WARNING - Disabling this process will cause the grid to malfunction. Re-enabling this setting will require manual effort via SSH.
|
||||||
forcedType: bool
|
forcedType: bool
|
||||||
advanced: True
|
advanced: True
|
||||||
readonly: True
|
|
||||||
helpLink: kratos
|
helpLink: kratos
|
||||||
|
|
||||||
oidc:
|
oidc:
|
||||||
enabled:
|
enabled:
|
||||||
description: Set to True to enable OIDC / Single Sign-On (SSO) to SOC. Requires a valid Security Onion license key.
|
description: Set to True to enable OIDC / Single Sign-On (SSO) to SOC. Requires a valid Security Onion license key.
|
||||||
@@ -21,12 +21,8 @@ kratos:
|
|||||||
description: "Specify the provider type. Required. Valid values are: auth0, generic, github, google, microsoft"
|
description: "Specify the provider type. Required. Valid values are: auth0, generic, github, google, microsoft"
|
||||||
global: True
|
global: True
|
||||||
forcedType: string
|
forcedType: string
|
||||||
options:
|
regex: "auth0|generic|github|google|microsoft"
|
||||||
- auth0
|
regexFailureMessage: "Valid values are: auth0, generic, github, google, microsoft"
|
||||||
- generic
|
|
||||||
- github
|
|
||||||
- google
|
|
||||||
- microsoft
|
|
||||||
helpLink: oidc
|
helpLink: oidc
|
||||||
client_id:
|
client_id:
|
||||||
description: Specify the client ID, also referenced as the application ID. Required.
|
description: Specify the client ID, also referenced as the application ID. Required.
|
||||||
@@ -47,9 +43,8 @@ kratos:
|
|||||||
description: The source of the subject identifier. Typically 'userinfo'. Only used when provider is 'microsoft'.
|
description: The source of the subject identifier. Typically 'userinfo'. Only used when provider is 'microsoft'.
|
||||||
global: True
|
global: True
|
||||||
forcedType: string
|
forcedType: string
|
||||||
options:
|
regex: me|userinfo
|
||||||
- me
|
regexFailureMessage: "Valid values are: me, userinfo"
|
||||||
- userinfo
|
|
||||||
helpLink: oidc
|
helpLink: oidc
|
||||||
auth_url:
|
auth_url:
|
||||||
description: Provider's auth URL. Required when provider is 'generic'.
|
description: Provider's auth URL. Required when provider is 'generic'.
|
||||||
@@ -103,7 +98,7 @@ kratos:
|
|||||||
config:
|
config:
|
||||||
session:
|
session:
|
||||||
lifespan:
|
lifespan:
|
||||||
description: Defines the length of a login session before it will timeout, and require a new login.
|
description: Defines the length of a login session.
|
||||||
global: True
|
global: True
|
||||||
helpLink: kratos
|
helpLink: kratos
|
||||||
whoami:
|
whoami:
|
||||||
|
|||||||
@@ -231,16 +231,6 @@ logrotate:
|
|||||||
- dateext
|
- dateext
|
||||||
- dateyesterday
|
- dateyesterday
|
||||||
- su root socore
|
- su root socore
|
||||||
/nsm/strelka/log/strelka_x_log:
|
|
||||||
- daily
|
|
||||||
- rotate 14
|
|
||||||
- missingok
|
|
||||||
- copytruncate
|
|
||||||
- compress
|
|
||||||
- create
|
|
||||||
- extension .log
|
|
||||||
- dateext
|
|
||||||
- dateyesterday
|
|
||||||
/opt/so/log/sensor_clean_x_log:
|
/opt/so/log/sensor_clean_x_log:
|
||||||
- daily
|
- daily
|
||||||
- rotate 2
|
- rotate 2
|
||||||
|
|||||||
@@ -147,13 +147,6 @@ logrotate:
|
|||||||
multiline: True
|
multiline: True
|
||||||
global: True
|
global: True
|
||||||
forcedType: "[]string"
|
forcedType: "[]string"
|
||||||
"/nsm/strelka/log/strelka_x_log":
|
|
||||||
description: List of logrotate options for this file.
|
|
||||||
title: /nsm/strelka/log/strelka.log
|
|
||||||
advanced: True
|
|
||||||
multiline: True
|
|
||||||
global: True
|
|
||||||
forcedType: "[]string"
|
|
||||||
"/opt/so/log/sensor_clean_x_log":
|
"/opt/so/log/sensor_clean_x_log":
|
||||||
description: List of logrotate options for this file.
|
description: List of logrotate options for this file.
|
||||||
title: /opt/so/log/sensor_clean.log
|
title: /opt/so/log/sensor_clean.log
|
||||||
|
|||||||
@@ -26,12 +26,12 @@ logstash:
|
|||||||
manager:
|
manager:
|
||||||
- so/0011_input_endgame.conf
|
- so/0011_input_endgame.conf
|
||||||
- so/0012_input_elastic_agent.conf.jinja
|
- so/0012_input_elastic_agent.conf.jinja
|
||||||
- so/0013_input_lumberjack_fleet.conf.jinja
|
- so/0013_input_lumberjack_fleet.conf
|
||||||
- so/9999_output_redis.conf.jinja
|
- so/9999_output_redis.conf.jinja
|
||||||
receiver:
|
receiver:
|
||||||
- so/0011_input_endgame.conf
|
- so/0011_input_endgame.conf
|
||||||
- so/0012_input_elastic_agent.conf.jinja
|
- so/0012_input_elastic_agent.conf.jinja
|
||||||
- so/0013_input_lumberjack_fleet.conf.jinja
|
- so/0013_input_lumberjack_fleet.conf
|
||||||
- so/9999_output_redis.conf.jinja
|
- so/9999_output_redis.conf.jinja
|
||||||
search:
|
search:
|
||||||
- so/0900_input_redis.conf.jinja
|
- so/0900_input_redis.conf.jinja
|
||||||
@@ -69,5 +69,4 @@ logstash:
|
|||||||
pipeline_x_batch_x_size: 125
|
pipeline_x_batch_x_size: 125
|
||||||
pipeline_x_ecs_compatibility: disabled
|
pipeline_x_ecs_compatibility: disabled
|
||||||
dmz_nodes: []
|
dmz_nodes: []
|
||||||
latency_metrics: False
|
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,6 @@ so-logstash:
|
|||||||
- /nsm/zeek:/nsm/zeek:ro
|
- /nsm/zeek:/nsm/zeek:ro
|
||||||
- /nsm/suricata:/suricata:ro
|
- /nsm/suricata:/suricata:ro
|
||||||
- /opt/so/log/fleet/:/osquery/logs:ro
|
- /opt/so/log/fleet/:/osquery/logs:ro
|
||||||
- /opt/so/log/strelka:/strelka:ro
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if DOCKERMERGED.containers['so-logstash'].custom_bind_mounts %}
|
{% if DOCKERMERGED.containers['so-logstash'].custom_bind_mounts %}
|
||||||
{% for BIND in DOCKERMERGED.containers['so-logstash'].custom_bind_mounts %}
|
{% for BIND in DOCKERMERGED.containers['so-logstash'].custom_bind_mounts %}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
{%- from 'logstash/map.jinja' import LOGSTASH_MERGED %}
|
|
||||||
input {
|
input {
|
||||||
elastic_agent {
|
elastic_agent {
|
||||||
port => 5055
|
port => 5055
|
||||||
@@ -12,15 +11,10 @@ input {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
filter {
|
filter {
|
||||||
{% if LOGSTASH_MERGED.get('latency_metrics', False) %}
|
if ![metadata] {
|
||||||
ruby {
|
mutate {
|
||||||
code => "event.set('[_tmp][logstash_from_agent]', Time.now().utc.iso8601(3));"
|
rename => {"@metadata" => "metadata"}
|
||||||
}
|
|
||||||
{% endif %}
|
|
||||||
if ![metadata] {
|
|
||||||
mutate {
|
|
||||||
rename => {"@metadata" => "metadata"}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
input {
|
||||||
|
elastic_agent {
|
||||||
|
port => 5056
|
||||||
|
tags => [ "elastic-agent", "fleet-lumberjack-input" ]
|
||||||
|
ssl_enabled => true
|
||||||
|
ssl_certificate => "/usr/share/logstash/elasticfleet-lumberjack.crt"
|
||||||
|
ssl_key => "/usr/share/logstash/elasticfleet-lumberjack.key"
|
||||||
|
ecs_compatibility => v8
|
||||||
|
id => "fleet-lumberjack-in"
|
||||||
|
codec => "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
filter {
|
||||||
|
if ![metadata] {
|
||||||
|
mutate {
|
||||||
|
rename => {"@metadata" => "metadata"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
{%- from 'logstash/map.jinja' import LOGSTASH_MERGED %}
|
|
||||||
input {
|
|
||||||
elastic_agent {
|
|
||||||
port => 5056
|
|
||||||
tags => [ "elastic-agent", "fleet-lumberjack-input" ]
|
|
||||||
ssl_enabled => true
|
|
||||||
ssl_certificate => "/usr/share/logstash/elasticfleet-lumberjack.crt"
|
|
||||||
ssl_key => "/usr/share/logstash/elasticfleet-lumberjack.key"
|
|
||||||
ecs_compatibility => v8
|
|
||||||
id => "fleet-lumberjack-in"
|
|
||||||
codec => "json"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filter {
|
|
||||||
{% if LOGSTASH_MERGED.get('latency_metrics', False) %}
|
|
||||||
ruby {
|
|
||||||
code => "event.set('[_tmp][logstash_from_fleet]', Time.now().utc.iso8601(3));"
|
|
||||||
}
|
|
||||||
{% endif %}
|
|
||||||
if ![metadata] {
|
|
||||||
mutate {
|
|
||||||
rename => {"@metadata" => "metadata"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
{%- from 'logstash/map.jinja' import LOGSTASH_MERGED %}
|
|
||||||
{%- set kafka_password = salt['pillar.get']('kafka:config:password') %}
|
{%- set kafka_password = salt['pillar.get']('kafka:config:password') %}
|
||||||
{%- set kafka_trustpass = salt['pillar.get']('kafka:config:trustpass') %}
|
{%- set kafka_trustpass = salt['pillar.get']('kafka:config:trustpass') %}
|
||||||
{%- set kafka_brokers = salt['pillar.get']('kafka:nodes', {}) %}
|
{%- set kafka_brokers = salt['pillar.get']('kafka:nodes', {}) %}
|
||||||
@@ -31,11 +30,6 @@ input {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
filter {
|
filter {
|
||||||
{% if LOGSTASH_MERGED.get('latency_metrics', False) %}
|
|
||||||
ruby {
|
|
||||||
code => "event.set('[_tmp][logstash_from_kafka]', Time.now().utc.iso8601(3));"
|
|
||||||
}
|
|
||||||
{% endif %}
|
|
||||||
if ![metadata] {
|
if ![metadata] {
|
||||||
mutate {
|
mutate {
|
||||||
rename => { "@metadata" => "metadata" }
|
rename => { "@metadata" => "metadata" }
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{%- from 'logstash/map.jinja' import LOGSTASH_REDIS_NODES, LOGSTASH_MERGED %}
|
{%- from 'logstash/map.jinja' import LOGSTASH_REDIS_NODES with context %}
|
||||||
{%- set REDIS_PASS = salt['pillar.get']('redis:config:requirepass') %}
|
{%- set REDIS_PASS = salt['pillar.get']('redis:config:requirepass') %}
|
||||||
|
|
||||||
{%- for index in range(LOGSTASH_REDIS_NODES|length) %}
|
{%- for index in range(LOGSTASH_REDIS_NODES|length) %}
|
||||||
@@ -18,10 +18,3 @@ input {
|
|||||||
}
|
}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor -%}
|
{% endfor -%}
|
||||||
filter {
|
|
||||||
{% if LOGSTASH_MERGED.get('latency_metrics', False) %}
|
|
||||||
ruby {
|
|
||||||
code => "event.set('[_tmp][logstash_from_redis]', Time.now().utc.iso8601(3));"
|
|
||||||
}
|
|
||||||
{% endif %}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
{%- from 'logstash/map.jinja' import LOGSTASH_MERGED %}
|
|
||||||
{% if LOGSTASH_MERGED.get('latency_metrics', False) %}
|
|
||||||
filter {
|
|
||||||
ruby {
|
|
||||||
code => "event.set('[_tmp][logstash_to_elasticsearch]', Time.now().utc.iso8601(3));"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{% endif %}
|
|
||||||
output {
|
output {
|
||||||
if "elastic-agent" in [tags] and "so-ip-mappings" in [tags] {
|
if "elastic-agent" in [tags] and "so-ip-mappings" in [tags] {
|
||||||
elasticsearch {
|
elasticsearch {
|
||||||
|
|||||||
@@ -13,20 +13,13 @@ filter {
|
|||||||
add_tag => "fleet-lumberjack-{{ GLOBALS.hostname }}"
|
add_tag => "fleet-lumberjack-{{ GLOBALS.hostname }}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{%- from 'logstash/map.jinja' import LOGSTASH_MERGED %}
|
|
||||||
{% if LOGSTASH_MERGED.get('latency_metrics', False) %}
|
output {
|
||||||
filter {
|
lumberjack {
|
||||||
ruby {
|
codec => json
|
||||||
code => "event.set('[_tmp][fleet_to_logstash]', Time.now().utc.iso8601(3));"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{% endif %}
|
|
||||||
output {
|
|
||||||
lumberjack {
|
|
||||||
codec => json
|
|
||||||
hosts => {{ FAILOVER_LOGSTASH_NODES }}
|
hosts => {{ FAILOVER_LOGSTASH_NODES }}
|
||||||
ssl_certificate => "/usr/share/filebeat/ca.crt"
|
ssl_certificate => "/usr/share/filebeat/ca.crt"
|
||||||
port => 5056
|
port => 5056
|
||||||
id => "fleet-lumberjack-{{ GLOBALS.hostname }}"
|
id => "fleet-lumberjack-{{ GLOBALS.hostname }}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,10 @@
|
|||||||
{%- from 'logstash/map.jinja' import LOGSTASH_MERGED %}
|
|
||||||
{%- if grains.role in ['so-heavynode', 'so-receiver'] %}
|
{%- if grains.role in ['so-heavynode', 'so-receiver'] %}
|
||||||
{%- set HOST = GLOBALS.hostname %}
|
{%- set HOST = GLOBALS.hostname %}
|
||||||
{%- else %}
|
{%- else %}
|
||||||
{%- set HOST = GLOBALS.manager %}
|
{%- set HOST = GLOBALS.manager %}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- set REDIS_PASS = salt['pillar.get']('redis:config:requirepass') %}
|
{%- set REDIS_PASS = salt['pillar.get']('redis:config:requirepass') %}
|
||||||
{% if LOGSTASH_MERGED.get('latency_metrics', False) %}
|
|
||||||
filter {
|
|
||||||
ruby {
|
|
||||||
code => "event.set('[_tmp][logstash_to_redis]', Time.now().utc.iso8601(3));"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{% endif %}
|
|
||||||
output {
|
output {
|
||||||
redis {
|
redis {
|
||||||
host => '{{ HOST }}'
|
host => '{{ HOST }}'
|
||||||
|
|||||||
@@ -86,8 +86,3 @@ logstash:
|
|||||||
multiline: True
|
multiline: True
|
||||||
advanced: True
|
advanced: True
|
||||||
forcedType: "[]string"
|
forcedType: "[]string"
|
||||||
latency_metrics:
|
|
||||||
description: Enable latency metrics within events processed by logstash. Useful for pinpointing log ingest delay.
|
|
||||||
forcedType: bool
|
|
||||||
global: False
|
|
||||||
advanced: True
|
|
||||||
|
|||||||
@@ -31,13 +31,11 @@ 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 the onlyif gates on the
|
# we dont want this added too early in setup, so we add the onlyif to verify 'startup_states: highstate'
|
||||||
# /opt/so/state/setup-complete marker. The marker is written by
|
# is in the minion config. That line is added before the final highstate during setup
|
||||||
# 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: "test -e /opt/so/state/setup-complete"
|
- onlyif: "grep -x 'startup_states: highstate' /etc/salt/minion"
|
||||||
|
|||||||
@@ -1,117 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
#
|
|
||||||
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
|
|
||||||
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
|
|
||||||
# https://securityonion.net/license; you may not use this file except in compliance with the
|
|
||||||
# Elastic License 2.0.
|
|
||||||
|
|
||||||
# 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
|
|
||||||
@@ -23,7 +23,6 @@ VALID_ROLES = {
|
|||||||
'a': { 'role': 'analyst','desc': 'Analyst - 80/tcp, 443/tcp' },
|
'a': { 'role': 'analyst','desc': 'Analyst - 80/tcp, 443/tcp' },
|
||||||
'b': { 'role': 'beats_endpoint', 'desc': 'Logstash Beat - 5044/tcp' },
|
'b': { 'role': 'beats_endpoint', 'desc': 'Logstash Beat - 5044/tcp' },
|
||||||
'e': { 'role': 'elasticsearch_rest', 'desc': 'Elasticsearch REST API - 9200/tcp' },
|
'e': { 'role': 'elasticsearch_rest', 'desc': 'Elasticsearch REST API - 9200/tcp' },
|
||||||
'f': { 'role': 'strelka_frontend', 'desc': 'Strelka frontend - 57314/tcp' },
|
|
||||||
's': { 'role': 'syslog', 'desc': 'Syslog device - 514/tcp/udp' },
|
's': { 'role': 'syslog', 'desc': 'Syslog device - 514/tcp/udp' },
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +90,6 @@ def main():
|
|||||||
group.add_argument('-a', dest='roles', action='append_const', const=VALID_ROLES['a']['role'], help="Analyst - 80/tcp, 443/tcp")
|
group.add_argument('-a', dest='roles', action='append_const', const=VALID_ROLES['a']['role'], help="Analyst - 80/tcp, 443/tcp")
|
||||||
group.add_argument('-b', dest='roles', action='append_const', const=VALID_ROLES['b']['role'], help="Logstash Beat - 5044/tcp")
|
group.add_argument('-b', dest='roles', action='append_const', const=VALID_ROLES['b']['role'], help="Logstash Beat - 5044/tcp")
|
||||||
group.add_argument('-e', dest='roles', action='append_const', const=VALID_ROLES['e']['role'], help="Elasticsearch REST API - 9200/tcp")
|
group.add_argument('-e', dest='roles', action='append_const', const=VALID_ROLES['e']['role'], help="Elasticsearch REST API - 9200/tcp")
|
||||||
group.add_argument('-f', dest='roles', action='append_const', const=VALID_ROLES['f']['role'], help="Strelka frontend - 57314/tcp")
|
|
||||||
group.add_argument('-s', dest='roles', action='append_const', const=VALID_ROLES['s']['role'], help="Syslog device - 514/tcp/udp")
|
group.add_argument('-s', dest='roles', action='append_const', const=VALID_ROLES['s']['role'], help="Syslog device - 514/tcp/udp")
|
||||||
|
|
||||||
ip_g = main_parser.add_argument_group(title='allow')
|
ip_g = main_parser.add_argument_group(title='allow')
|
||||||
|
|||||||
@@ -1,381 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
# Imports detection overrides (e.g. from so-detections-backup) into the so-detection
|
|
||||||
# index. Reads <publicId>.<ext> files (NDJSON, one override per line) from a source
|
|
||||||
# directory, looks up the matching detection by publicId+engine, validates each
|
|
||||||
# override against the same rules SOC enforces, dedupes against existing overrides
|
|
||||||
# (operational fields only), and appends new ones.
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import ipaddress
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
import requests
|
|
||||||
from requests.auth import HTTPBasicAuth
|
|
||||||
import urllib3
|
|
||||||
|
|
||||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
||||||
|
|
||||||
DEFAULT_INDEX = "so-detection"
|
|
||||||
AUTH_FILE = "/opt/so/conf/elasticsearch/curl.config"
|
|
||||||
ES_URL = "https://localhost:9200"
|
|
||||||
|
|
||||||
# Engines we know how to handle and the file extension the backup script writes.
|
|
||||||
ENGINES = {
|
|
||||||
"suricata": "txt",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Standard Suricata variables that ship with Security Onion. Anything else
|
|
||||||
# referenced in an override is "custom" and the user needs to make sure it
|
|
||||||
# exists in SOC Config before the override will function.
|
|
||||||
BUILTIN_SURICATA_VARS = {
|
|
||||||
"$HOME_NET", "$EXTERNAL_NET",
|
|
||||||
"$HTTP_SERVERS", "$DNS_SERVERS", "$SQL_SERVERS", "$SMTP_SERVERS",
|
|
||||||
"$TELNET_SERVERS", "$AIM_SERVERS", "$DC_SERVERS", "$MODBUS_SERVER",
|
|
||||||
"$MODBUS_CLIENT", "$ENIP_CLIENT", "$ENIP_SERVER",
|
|
||||||
"$HTTP_PORTS", "$SHELLCODE_PORTS", "$ORACLE_PORTS", "$SSH_PORTS",
|
|
||||||
"$FTP_PORTS", "$FILE_DATA_PORTS",
|
|
||||||
}
|
|
||||||
|
|
||||||
VAR_PATTERN = re.compile(r"\$[A-Z_][A-Z0-9_]*")
|
|
||||||
|
|
||||||
# Canonical valid values, per securityonion-soc/model/detection.go.
|
|
||||||
SURICATA_OVERRIDE_TYPES = {"suppress", "threshold", "modify"}
|
|
||||||
SUPPRESS_TRACKS = {"by_src", "by_dst", "by_either"}
|
|
||||||
THRESHOLD_TRACKS = {"by_src", "by_dst", "by_both"}
|
|
||||||
THRESHOLD_TYPES = {"limit", "threshold", "both"}
|
|
||||||
|
|
||||||
STALE_WARNING = """\
|
|
||||||
WARNING: so-detections-backup does not remove backup files when overrides are
|
|
||||||
deleted via the Security Onion web UI. As a result, files in the source
|
|
||||||
directory may represent overrides that were intentionally deleted and should
|
|
||||||
NOT be re-imported.
|
|
||||||
|
|
||||||
Before continuing, verify that the source directory reflects the overrides you
|
|
||||||
actually want imported. Remove any files corresponding to overrides you previously deleted.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def make_session(auth_file):
|
|
||||||
with open(auth_file, "r") as f:
|
|
||||||
for line in f:
|
|
||||||
if line.startswith("user ="):
|
|
||||||
creds = line.split("=", 1)[1].strip().replace('"', "")
|
|
||||||
user, _, password = creds.partition(":")
|
|
||||||
session = requests.Session()
|
|
||||||
session.auth = HTTPBasicAuth(user, password)
|
|
||||||
session.headers.update({"Content-Type": "application/json"})
|
|
||||||
session.verify = False
|
|
||||||
return session
|
|
||||||
raise RuntimeError(f"Could not find 'user =' line in {auth_file}")
|
|
||||||
|
|
||||||
|
|
||||||
def find_detection(session, index, public_id, engine):
|
|
||||||
query = {
|
|
||||||
"query": {"bool": {"must": [
|
|
||||||
{"term": {"so_detection.publicId": public_id}},
|
|
||||||
{"term": {"so_detection.engine": engine}},
|
|
||||||
]}},
|
|
||||||
"size": 2,
|
|
||||||
}
|
|
||||||
r = session.get(f"{ES_URL}/{index}/_search", json=query)
|
|
||||||
r.raise_for_status()
|
|
||||||
hits = r.json().get("hits", {}).get("hits", [])
|
|
||||||
if not hits:
|
|
||||||
return None, None, None
|
|
||||||
if len(hits) > 1:
|
|
||||||
# Shouldn't happen — publicId is unique per engine — but flag it.
|
|
||||||
print(f" WARN: {len(hits)} detections matched publicId={public_id} engine={engine}; using first")
|
|
||||||
hit = hits[0]
|
|
||||||
existing = hit["_source"].get("so_detection", {}).get("overrides") or []
|
|
||||||
return hit["_id"], hit["_index"], existing
|
|
||||||
|
|
||||||
|
|
||||||
def update_overrides(session, doc_index, doc_id, overrides):
|
|
||||||
body = {"doc": {"so_detection": {"overrides": overrides}}}
|
|
||||||
r = session.post(f"{ES_URL}/{doc_index}/_update/{doc_id}", json=body)
|
|
||||||
r.raise_for_status()
|
|
||||||
return r.json()
|
|
||||||
|
|
||||||
|
|
||||||
def dedupe_key(override):
|
|
||||||
"""Operational fields only, per Override.Equal() in detection.go.
|
|
||||||
Excludes timestamps and isEnabled so re-imports don't appear unique."""
|
|
||||||
t = override.get("type")
|
|
||||||
if t == "suppress":
|
|
||||||
return (t, override.get("track"), override.get("ip"))
|
|
||||||
if t == "threshold":
|
|
||||||
return (t, override.get("thresholdType"), override.get("track"),
|
|
||||||
override.get("count"), override.get("seconds"))
|
|
||||||
if t == "modify":
|
|
||||||
return (t, override.get("regex"), override.get("value"))
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_suricata_ip(ip):
|
|
||||||
if not ip:
|
|
||||||
return "ip cannot be empty"
|
|
||||||
if ip.startswith("$"):
|
|
||||||
return None
|
|
||||||
if ip.startswith("[") and ip.endswith("]"):
|
|
||||||
for part in ip[1:-1].split(","):
|
|
||||||
err = _validate_single_ip(part.strip())
|
|
||||||
if err:
|
|
||||||
return f"invalid IP in list: {err}"
|
|
||||||
return None
|
|
||||||
return _validate_single_ip(ip)
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_single_ip(ip):
|
|
||||||
try:
|
|
||||||
if "/" in ip:
|
|
||||||
ipaddress.ip_network(ip, strict=False)
|
|
||||||
else:
|
|
||||||
ipaddress.ip_address(ip)
|
|
||||||
except ValueError:
|
|
||||||
return f"invalid IP/CIDR {ip!r}"
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def validate_override(override, engine):
|
|
||||||
"""Mirror Override.Validate() from securityonion-soc/model/detection.go.
|
|
||||||
Returns None on success, an error string otherwise."""
|
|
||||||
t = override.get("type")
|
|
||||||
if not t:
|
|
||||||
return "override type is required"
|
|
||||||
if t not in SURICATA_OVERRIDE_TYPES:
|
|
||||||
return f"invalid type {t!r}: must be one of {sorted(SURICATA_OVERRIDE_TYPES)}"
|
|
||||||
|
|
||||||
has = {k: override.get(k) is not None for k in
|
|
||||||
("regex", "value", "thresholdType", "track", "ip", "count", "seconds", "customFilter")}
|
|
||||||
|
|
||||||
if t == "suppress":
|
|
||||||
if not has["ip"] or not has["track"]:
|
|
||||||
return "suppress requires 'ip' and 'track'"
|
|
||||||
if any(has[k] for k in ("regex", "value", "thresholdType", "count", "seconds", "customFilter")):
|
|
||||||
return "suppress has unnecessary fields"
|
|
||||||
if override["track"] not in SUPPRESS_TRACKS:
|
|
||||||
return f"invalid track {override['track']!r}: must be one of {sorted(SUPPRESS_TRACKS)}"
|
|
||||||
return _validate_suricata_ip(override["ip"])
|
|
||||||
|
|
||||||
if t == "threshold":
|
|
||||||
if not all(has[k] for k in ("thresholdType", "track", "count", "seconds")):
|
|
||||||
return "threshold requires 'thresholdType', 'track', 'count', 'seconds'"
|
|
||||||
if any(has[k] for k in ("regex", "value", "customFilter")):
|
|
||||||
return "threshold has unnecessary fields"
|
|
||||||
if override["thresholdType"] not in THRESHOLD_TYPES:
|
|
||||||
return f"invalid thresholdType {override['thresholdType']!r}: must be one of {sorted(THRESHOLD_TYPES)}"
|
|
||||||
if override["track"] not in THRESHOLD_TRACKS:
|
|
||||||
return f"invalid track {override['track']!r}: must be one of {sorted(THRESHOLD_TRACKS)}"
|
|
||||||
if not isinstance(override["count"], int) or override["count"] <= 0:
|
|
||||||
return f"count must be a positive integer, got {override['count']!r}"
|
|
||||||
if not isinstance(override["seconds"], int) or override["seconds"] <= 0:
|
|
||||||
return f"seconds must be a positive integer, got {override['seconds']!r}"
|
|
||||||
return None
|
|
||||||
|
|
||||||
if t == "modify":
|
|
||||||
if not has["regex"] or not has["value"]:
|
|
||||||
return "modify requires 'regex' and 'value'"
|
|
||||||
if any(has[k] for k in ("thresholdType", "track", "count", "seconds", "customFilter")):
|
|
||||||
return "modify has unnecessary fields"
|
|
||||||
try:
|
|
||||||
re.compile(override["regex"])
|
|
||||||
except re.error as e:
|
|
||||||
return f"invalid regex: {e}"
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def parse_overrides_file(path):
|
|
||||||
"""Parse a file written by so-detections-backup.py: NDJSON, one override
|
|
||||||
per line. Returns a list of (override_dict, line_number)."""
|
|
||||||
overrides = []
|
|
||||||
with open(path, "r") as f:
|
|
||||||
for i, line in enumerate(f, start=1):
|
|
||||||
line = line.strip()
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
overrides.append((json.loads(line), i))
|
|
||||||
return overrides
|
|
||||||
|
|
||||||
|
|
||||||
def describe(override):
|
|
||||||
"""Human-readable summary of the operational fields for a given override type."""
|
|
||||||
t = override.get("type")
|
|
||||||
if t == "suppress":
|
|
||||||
return f"type=suppress track={override.get('track')} ip={override.get('ip')}"
|
|
||||||
if t == "threshold":
|
|
||||||
return (f"type=threshold track={override.get('track')} "
|
|
||||||
f"thresholdType={override.get('thresholdType')} "
|
|
||||||
f"count={override.get('count')} seconds={override.get('seconds')}")
|
|
||||||
if t == "modify":
|
|
||||||
return f"type=modify regex={override.get('regex')!r}"
|
|
||||||
|
|
||||||
|
|
||||||
def collect_custom_vars(override):
|
|
||||||
found = set()
|
|
||||||
for value in override.values():
|
|
||||||
if isinstance(value, str):
|
|
||||||
for match in VAR_PATTERN.findall(value):
|
|
||||||
if match not in BUILTIN_SURICATA_VARS:
|
|
||||||
found.add(match)
|
|
||||||
return found
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
|
||||||
p = argparse.ArgumentParser(
|
|
||||||
description="Import detection overrides into the so-detection index.",
|
|
||||||
)
|
|
||||||
p.add_argument("--source", "-s", required=True,
|
|
||||||
help="Source directory containing <publicId>.<ext> override files.")
|
|
||||||
p.add_argument("--engine", "-e", default="suricata", choices=list(ENGINES.keys()),
|
|
||||||
help="Detection engine (default: suricata).")
|
|
||||||
p.add_argument("--dry-run", "-n", action="store_true",
|
|
||||||
help="Print what would happen without writing to Elasticsearch.")
|
|
||||||
p.add_argument("--no-import-note", action="store_true",
|
|
||||||
help="Do not prepend '[Imported YYYY-MM-DD] ' to the override note.")
|
|
||||||
p.add_argument("--index", "-i", default=DEFAULT_INDEX,
|
|
||||||
help=f"Elasticsearch index to update (default: {DEFAULT_INDEX}).")
|
|
||||||
return p.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
def confirm_proceed(args):
|
|
||||||
"""Show the stale-backup warning. Dry-run prints it and continues. Real
|
|
||||||
runs require the user typing 'yes' at the prompt."""
|
|
||||||
print(STALE_WARNING)
|
|
||||||
if args.dry_run:
|
|
||||||
print("(dry-run: no acknowledgement required)\n")
|
|
||||||
return True
|
|
||||||
answer = input("Type 'yes' to acknowledge and continue: ").strip().lower()
|
|
||||||
print()
|
|
||||||
return answer == "yes"
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
args = parse_args()
|
|
||||||
|
|
||||||
if not os.path.isdir(args.source):
|
|
||||||
print(f"ERROR: source directory not found: {args.source}", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
extension = ENGINES[args.engine]
|
|
||||||
files = sorted(f for f in os.listdir(args.source) if f.endswith(f".{extension}"))
|
|
||||||
if not files:
|
|
||||||
print(f"No *.{extension} files found in {args.source}")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
if not confirm_proceed(args):
|
|
||||||
print("Aborted.")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
session = make_session(AUTH_FILE)
|
|
||||||
today = datetime.now().strftime("%Y-%m-%d")
|
|
||||||
note_prefix = "" if args.no_import_note else f"[Imported {today}] "
|
|
||||||
|
|
||||||
counts = {"added": 0, "skipped_dedupe": 0, "skipped_not_found": 0, "invalid": 0, "error": 0}
|
|
||||||
custom_vars = set()
|
|
||||||
|
|
||||||
mode = "DRY-RUN" if args.dry_run else "IMPORT"
|
|
||||||
print(f"[{mode}] engine={args.engine} source={args.source} index={args.index}\n")
|
|
||||||
|
|
||||||
for filename in files:
|
|
||||||
public_id = os.path.splitext(filename)[0]
|
|
||||||
path = os.path.join(args.source, filename)
|
|
||||||
print(f"{public_id}:")
|
|
||||||
|
|
||||||
try:
|
|
||||||
new_overrides = parse_overrides_file(path)
|
|
||||||
except (json.JSONDecodeError, OSError) as e:
|
|
||||||
print(f" ERROR: could not parse {filename}: {e}")
|
|
||||||
counts["error"] += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not new_overrides:
|
|
||||||
print(" SKIP: empty file")
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
doc_id, doc_index, existing = find_detection(session, args.index, public_id, args.engine)
|
|
||||||
except requests.HTTPError as e:
|
|
||||||
print(f" ERROR: search failed: {e}")
|
|
||||||
counts["error"] += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
if doc_id is None:
|
|
||||||
print(f" WARN: no detection found for publicId={public_id} engine={args.engine}; skipping")
|
|
||||||
counts["skipped_not_found"] += len(new_overrides)
|
|
||||||
continue
|
|
||||||
|
|
||||||
existing_keys = {dedupe_key(o) for o in existing}
|
|
||||||
merged = list(existing)
|
|
||||||
added_this_file = 0
|
|
||||||
|
|
||||||
for override, line_no in new_overrides:
|
|
||||||
err = validate_override(override, args.engine)
|
|
||||||
if err:
|
|
||||||
print(f" INVALID (line {line_no}): {err}")
|
|
||||||
counts["invalid"] += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
custom_vars.update(collect_custom_vars(override))
|
|
||||||
key = dedupe_key(override)
|
|
||||||
if key in existing_keys:
|
|
||||||
print(f" SKIP (line {line_no}): duplicate of existing override [{describe(override)}]")
|
|
||||||
counts["skipped_dedupe"] += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
if note_prefix:
|
|
||||||
override = dict(override)
|
|
||||||
override["note"] = note_prefix + (override.get("note") or "")
|
|
||||||
|
|
||||||
merged.append(override)
|
|
||||||
existing_keys.add(key)
|
|
||||||
added_this_file += 1
|
|
||||||
print(f" ADD (line {line_no}): {describe(override)}")
|
|
||||||
|
|
||||||
if added_this_file == 0:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if args.dry_run:
|
|
||||||
print(f" DRY-RUN: would update {doc_index}/{doc_id} "
|
|
||||||
f"({len(existing)} existing → {len(merged)} total)")
|
|
||||||
counts["added"] += added_this_file
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
update_overrides(session, doc_index, doc_id, merged)
|
|
||||||
print(f" UPDATED {doc_index}/{doc_id} ({len(existing)} → {len(merged)})")
|
|
||||||
counts["added"] += added_this_file
|
|
||||||
except requests.HTTPError as e:
|
|
||||||
print(f" ERROR: update failed: {e}")
|
|
||||||
counts["error"] += 1
|
|
||||||
|
|
||||||
print()
|
|
||||||
print("=" * 60)
|
|
||||||
print(f"Summary ({mode}):")
|
|
||||||
print(f" Overrides added: {counts['added']}")
|
|
||||||
print(f" Skipped (already present): {counts['skipped_dedupe']}")
|
|
||||||
print(f" Skipped (no detection): {counts['skipped_not_found']}")
|
|
||||||
print(f" Invalid (failed checks): {counts['invalid']}")
|
|
||||||
print(f" Errors: {counts['error']}")
|
|
||||||
|
|
||||||
if custom_vars:
|
|
||||||
print()
|
|
||||||
print("WARNING: detected custom Suricata variables in imported overrides:")
|
|
||||||
for v in sorted(custom_vars):
|
|
||||||
print(f" {v}")
|
|
||||||
print("If any of these are not already defined in SOC Config (Suricata variables),")
|
|
||||||
print("you must add them manually before the rules will function correctly.")
|
|
||||||
|
|
||||||
sys.exit(0 if counts["error"] == 0 and counts["invalid"] == 0 else 1)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,588 +0,0 @@
|
|||||||
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
|
|
||||||
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
|
|
||||||
# https://securityonion.net/license; you may not use this file except in compliance with the
|
|
||||||
# Elastic License 2.0.
|
|
||||||
|
|
||||||
import importlib.util
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import unittest
|
|
||||||
from importlib.machinery import SourceFileLoader
|
|
||||||
from io import StringIO
|
|
||||||
from unittest.mock import MagicMock, patch
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
# The script has no .py extension; spec_from_file_location can't auto-detect a
|
|
||||||
# loader, so we hand it a SourceFileLoader explicitly. (load_module() is
|
|
||||||
# deprecated in 3.14 and slated for removal in 3.15.)
|
|
||||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
SCRIPT = os.path.join(HERE, "so-detections-overrides-import")
|
|
||||||
_loader = SourceFileLoader("so_overrides_import", SCRIPT)
|
|
||||||
_spec = importlib.util.spec_from_loader("so_overrides_import", _loader)
|
|
||||||
soi = importlib.util.module_from_spec(_spec)
|
|
||||||
_loader.exec_module(soi)
|
|
||||||
|
|
||||||
|
|
||||||
class TestValidateSuppress(unittest.TestCase):
|
|
||||||
def test_valid(self):
|
|
||||||
self.assertIsNone(soi.validate_override(
|
|
||||||
{"type": "suppress", "track": "by_src", "ip": "1.2.3.4"}, "suricata"))
|
|
||||||
|
|
||||||
def test_valid_var(self):
|
|
||||||
self.assertIsNone(soi.validate_override(
|
|
||||||
{"type": "suppress", "track": "by_either", "ip": "$HOME_NET"}, "suricata"))
|
|
||||||
|
|
||||||
def test_valid_cidr(self):
|
|
||||||
self.assertIsNone(soi.validate_override(
|
|
||||||
{"type": "suppress", "track": "by_dst", "ip": "10.0.0.0/8"}, "suricata"))
|
|
||||||
|
|
||||||
def test_valid_bracket_list(self):
|
|
||||||
self.assertIsNone(soi.validate_override(
|
|
||||||
{"type": "suppress", "track": "by_src", "ip": "[1.2.3.4,10.0.0.0/8]"}, "suricata"))
|
|
||||||
|
|
||||||
def test_missing_ip(self):
|
|
||||||
err = soi.validate_override({"type": "suppress", "track": "by_src"}, "suricata")
|
|
||||||
self.assertIn("requires", err)
|
|
||||||
|
|
||||||
def test_missing_track(self):
|
|
||||||
err = soi.validate_override({"type": "suppress", "ip": "1.2.3.4"}, "suricata")
|
|
||||||
self.assertIn("requires", err)
|
|
||||||
|
|
||||||
def test_invalid_track(self):
|
|
||||||
err = soi.validate_override(
|
|
||||||
{"type": "suppress", "track": "by_both", "ip": "1.2.3.4"}, "suricata")
|
|
||||||
self.assertIn("invalid track", err)
|
|
||||||
|
|
||||||
def test_invalid_ip(self):
|
|
||||||
err = soi.validate_override(
|
|
||||||
{"type": "suppress", "track": "by_src", "ip": "not-an-ip"}, "suricata")
|
|
||||||
self.assertIn("invalid IP", err)
|
|
||||||
|
|
||||||
def test_unnecessary_field(self):
|
|
||||||
err = soi.validate_override(
|
|
||||||
{"type": "suppress", "track": "by_src", "ip": "1.2.3.4", "count": 5}, "suricata")
|
|
||||||
self.assertIn("unnecessary fields", err)
|
|
||||||
|
|
||||||
|
|
||||||
class TestValidateThreshold(unittest.TestCase):
|
|
||||||
def test_valid(self):
|
|
||||||
self.assertIsNone(soi.validate_override({
|
|
||||||
"type": "threshold", "track": "by_src",
|
|
||||||
"thresholdType": "limit", "count": 10, "seconds": 60,
|
|
||||||
}, "suricata"))
|
|
||||||
|
|
||||||
def test_valid_by_both(self):
|
|
||||||
self.assertIsNone(soi.validate_override({
|
|
||||||
"type": "threshold", "track": "by_both",
|
|
||||||
"thresholdType": "both", "count": 1, "seconds": 1,
|
|
||||||
}, "suricata"))
|
|
||||||
|
|
||||||
def test_track_by_either_invalid(self):
|
|
||||||
err = soi.validate_override({
|
|
||||||
"type": "threshold", "track": "by_either",
|
|
||||||
"thresholdType": "limit", "count": 10, "seconds": 60,
|
|
||||||
}, "suricata")
|
|
||||||
self.assertIn("invalid track", err)
|
|
||||||
|
|
||||||
def test_invalid_threshold_type(self):
|
|
||||||
err = soi.validate_override({
|
|
||||||
"type": "threshold", "track": "by_src",
|
|
||||||
"thresholdType": "bogus", "count": 10, "seconds": 60,
|
|
||||||
}, "suricata")
|
|
||||||
self.assertIn("invalid thresholdType", err)
|
|
||||||
|
|
||||||
def test_zero_count(self):
|
|
||||||
err = soi.validate_override({
|
|
||||||
"type": "threshold", "track": "by_src",
|
|
||||||
"thresholdType": "limit", "count": 0, "seconds": 60,
|
|
||||||
}, "suricata")
|
|
||||||
self.assertIn("count", err)
|
|
||||||
|
|
||||||
def test_negative_seconds(self):
|
|
||||||
err = soi.validate_override({
|
|
||||||
"type": "threshold", "track": "by_src",
|
|
||||||
"thresholdType": "limit", "count": 10, "seconds": -1,
|
|
||||||
}, "suricata")
|
|
||||||
self.assertIn("seconds", err)
|
|
||||||
|
|
||||||
def test_missing_field(self):
|
|
||||||
err = soi.validate_override({
|
|
||||||
"type": "threshold", "track": "by_src",
|
|
||||||
"thresholdType": "limit", "count": 10, # missing seconds
|
|
||||||
}, "suricata")
|
|
||||||
self.assertIn("requires", err)
|
|
||||||
|
|
||||||
def test_unnecessary_field(self):
|
|
||||||
err = soi.validate_override({
|
|
||||||
"type": "threshold", "track": "by_src",
|
|
||||||
"thresholdType": "limit", "count": 10, "seconds": 60,
|
|
||||||
"regex": "foo",
|
|
||||||
}, "suricata")
|
|
||||||
self.assertIn("unnecessary fields", err)
|
|
||||||
|
|
||||||
|
|
||||||
class TestValidateModify(unittest.TestCase):
|
|
||||||
def test_valid(self):
|
|
||||||
self.assertIsNone(soi.validate_override(
|
|
||||||
{"type": "modify", "regex": r"content:\"foo\"", "value": "content:bar"}, "suricata"))
|
|
||||||
|
|
||||||
def test_invalid_regex(self):
|
|
||||||
err = soi.validate_override(
|
|
||||||
{"type": "modify", "regex": "(unbalanced", "value": "x"}, "suricata")
|
|
||||||
self.assertIn("invalid regex", err)
|
|
||||||
|
|
||||||
def test_missing_value(self):
|
|
||||||
err = soi.validate_override({"type": "modify", "regex": "x"}, "suricata")
|
|
||||||
self.assertIn("requires", err)
|
|
||||||
|
|
||||||
def test_unnecessary_field(self):
|
|
||||||
err = soi.validate_override(
|
|
||||||
{"type": "modify", "regex": "x", "value": "y", "track": "by_src"}, "suricata")
|
|
||||||
self.assertIn("unnecessary fields", err)
|
|
||||||
|
|
||||||
|
|
||||||
class TestValidateMisc(unittest.TestCase):
|
|
||||||
def test_unknown_type(self):
|
|
||||||
err = soi.validate_override({"type": "suppresss", "track": "by_src", "ip": "1.2.3.4"}, "suricata")
|
|
||||||
self.assertIn("invalid type", err)
|
|
||||||
|
|
||||||
def test_missing_type(self):
|
|
||||||
err = soi.validate_override({"track": "by_src"}, "suricata")
|
|
||||||
self.assertIn("type is required", err)
|
|
||||||
|
|
||||||
|
|
||||||
class TestValidateIP(unittest.TestCase):
|
|
||||||
def test_plain_ipv4(self):
|
|
||||||
self.assertIsNone(soi._validate_suricata_ip("1.2.3.4"))
|
|
||||||
|
|
||||||
def test_plain_ipv6(self):
|
|
||||||
self.assertIsNone(soi._validate_suricata_ip("::1"))
|
|
||||||
|
|
||||||
def test_cidr(self):
|
|
||||||
self.assertIsNone(soi._validate_suricata_ip("10.0.0.0/8"))
|
|
||||||
|
|
||||||
def test_var(self):
|
|
||||||
self.assertIsNone(soi._validate_suricata_ip("$CONCOURSEWORKERS"))
|
|
||||||
|
|
||||||
def test_bracket_list(self):
|
|
||||||
self.assertIsNone(soi._validate_suricata_ip("[1.2.3.4, 10.0.0.0/8]"))
|
|
||||||
|
|
||||||
def test_bracket_list_bad_member(self):
|
|
||||||
err = soi._validate_suricata_ip("[1.2.3.4,nope]")
|
|
||||||
self.assertIn("invalid IP in list", err)
|
|
||||||
|
|
||||||
def test_empty(self):
|
|
||||||
self.assertIn("empty", soi._validate_suricata_ip(""))
|
|
||||||
|
|
||||||
def test_invalid(self):
|
|
||||||
self.assertIn("invalid", soi._validate_suricata_ip("999.999.999.999"))
|
|
||||||
|
|
||||||
|
|
||||||
class TestDedupeKey(unittest.TestCase):
|
|
||||||
def test_suppress(self):
|
|
||||||
a = {"type": "suppress", "track": "by_src", "ip": "1.2.3.4", "count": 99}
|
|
||||||
b = {"type": "suppress", "track": "by_src", "ip": "1.2.3.4"}
|
|
||||||
# count is irrelevant for suppress dedupe
|
|
||||||
self.assertEqual(soi.dedupe_key(a), soi.dedupe_key(b))
|
|
||||||
|
|
||||||
def test_suppress_differs_on_ip(self):
|
|
||||||
a = {"type": "suppress", "track": "by_src", "ip": "1.2.3.4"}
|
|
||||||
b = {"type": "suppress", "track": "by_src", "ip": "5.6.7.8"}
|
|
||||||
self.assertNotEqual(soi.dedupe_key(a), soi.dedupe_key(b))
|
|
||||||
|
|
||||||
def test_threshold(self):
|
|
||||||
a = {"type": "threshold", "track": "by_src", "thresholdType": "limit",
|
|
||||||
"count": 10, "seconds": 60, "ip": "ignored"}
|
|
||||||
b = {"type": "threshold", "track": "by_src", "thresholdType": "limit",
|
|
||||||
"count": 10, "seconds": 60}
|
|
||||||
self.assertEqual(soi.dedupe_key(a), soi.dedupe_key(b))
|
|
||||||
|
|
||||||
def test_threshold_differs_on_count(self):
|
|
||||||
a = {"type": "threshold", "track": "by_src", "thresholdType": "limit",
|
|
||||||
"count": 10, "seconds": 60}
|
|
||||||
b = {"type": "threshold", "track": "by_src", "thresholdType": "limit",
|
|
||||||
"count": 20, "seconds": 60}
|
|
||||||
self.assertNotEqual(soi.dedupe_key(a), soi.dedupe_key(b))
|
|
||||||
|
|
||||||
def test_modify(self):
|
|
||||||
a = {"type": "modify", "regex": "x", "value": "y"}
|
|
||||||
b = {"type": "modify", "regex": "x", "value": "y"}
|
|
||||||
self.assertEqual(soi.dedupe_key(a), soi.dedupe_key(b))
|
|
||||||
|
|
||||||
|
|
||||||
class TestDescribe(unittest.TestCase):
|
|
||||||
def test_suppress(self):
|
|
||||||
s = soi.describe({"type": "suppress", "track": "by_src", "ip": "1.2.3.4"})
|
|
||||||
self.assertIn("suppress", s)
|
|
||||||
self.assertIn("by_src", s)
|
|
||||||
self.assertIn("1.2.3.4", s)
|
|
||||||
|
|
||||||
def test_threshold_includes_count(self):
|
|
||||||
s = soi.describe({"type": "threshold", "track": "by_src",
|
|
||||||
"thresholdType": "limit", "count": 10, "seconds": 60})
|
|
||||||
self.assertIn("count=10", s)
|
|
||||||
self.assertIn("seconds=60", s)
|
|
||||||
|
|
||||||
def test_modify(self):
|
|
||||||
s = soi.describe({"type": "modify", "regex": "foo"})
|
|
||||||
self.assertIn("modify", s)
|
|
||||||
self.assertIn("foo", s)
|
|
||||||
|
|
||||||
|
|
||||||
class TestParseOverridesFile(unittest.TestCase):
|
|
||||||
def _write(self, content):
|
|
||||||
fd, path = tempfile.mkstemp(suffix=".txt")
|
|
||||||
os.close(fd)
|
|
||||||
with open(path, "w") as f:
|
|
||||||
f.write(content)
|
|
||||||
self.addCleanup(os.unlink, path)
|
|
||||||
return path
|
|
||||||
|
|
||||||
def test_single_line(self):
|
|
||||||
path = self._write('{"type":"suppress","track":"by_src","ip":"1.2.3.4"}')
|
|
||||||
result = soi.parse_overrides_file(path)
|
|
||||||
self.assertEqual(len(result), 1)
|
|
||||||
self.assertEqual(result[0][0]["type"], "suppress")
|
|
||||||
self.assertEqual(result[0][1], 1)
|
|
||||||
|
|
||||||
def test_ndjson(self):
|
|
||||||
path = self._write(
|
|
||||||
'{"type":"suppress","track":"by_src","ip":"1.2.3.4"}\n'
|
|
||||||
'{"type":"suppress","track":"by_dst","ip":"5.6.7.8"}\n'
|
|
||||||
)
|
|
||||||
result = soi.parse_overrides_file(path)
|
|
||||||
self.assertEqual(len(result), 2)
|
|
||||||
self.assertEqual(result[1][1], 2)
|
|
||||||
|
|
||||||
def test_empty(self):
|
|
||||||
path = self._write("")
|
|
||||||
self.assertEqual(soi.parse_overrides_file(path), [])
|
|
||||||
|
|
||||||
def test_blank_lines_skipped(self):
|
|
||||||
path = self._write('\n{"type":"suppress","track":"by_src","ip":"1.2.3.4"}\n\n')
|
|
||||||
result = soi.parse_overrides_file(path)
|
|
||||||
self.assertEqual(len(result), 1)
|
|
||||||
self.assertEqual(result[0][1], 2) # line number reflects original position
|
|
||||||
|
|
||||||
def test_invalid_raises(self):
|
|
||||||
path = self._write("not json")
|
|
||||||
with self.assertRaises(json.JSONDecodeError):
|
|
||||||
soi.parse_overrides_file(path)
|
|
||||||
|
|
||||||
|
|
||||||
class TestCollectCustomVars(unittest.TestCase):
|
|
||||||
def test_finds_custom(self):
|
|
||||||
v = soi.collect_custom_vars({"ip": "$CONCOURSEWORKERS"})
|
|
||||||
self.assertEqual(v, {"$CONCOURSEWORKERS"})
|
|
||||||
|
|
||||||
def test_filters_builtins(self):
|
|
||||||
v = soi.collect_custom_vars({"ip": "$HOME_NET"})
|
|
||||||
self.assertEqual(v, set())
|
|
||||||
|
|
||||||
def test_mixed(self):
|
|
||||||
v = soi.collect_custom_vars({"ip": "[$HOME_NET,$MYNET]"})
|
|
||||||
self.assertEqual(v, {"$MYNET"})
|
|
||||||
|
|
||||||
def test_non_string_fields_ignored(self):
|
|
||||||
v = soi.collect_custom_vars({"count": 10, "isEnabled": True})
|
|
||||||
self.assertEqual(v, set())
|
|
||||||
|
|
||||||
|
|
||||||
class TestMakeSession(unittest.TestCase):
|
|
||||||
def _write(self, content):
|
|
||||||
fd, path = tempfile.mkstemp()
|
|
||||||
os.close(fd)
|
|
||||||
with open(path, "w") as f:
|
|
||||||
f.write(content)
|
|
||||||
self.addCleanup(os.unlink, path)
|
|
||||||
return path
|
|
||||||
|
|
||||||
def test_valid_auth_file(self):
|
|
||||||
path = self._write('user = "admin:secret"\n')
|
|
||||||
session = soi.make_session(path)
|
|
||||||
self.assertEqual(session.auth.username, "admin")
|
|
||||||
self.assertEqual(session.auth.password, "secret")
|
|
||||||
self.assertFalse(session.verify)
|
|
||||||
|
|
||||||
def test_missing_user_line(self):
|
|
||||||
path = self._write("# no user line here\n")
|
|
||||||
with self.assertRaises(RuntimeError):
|
|
||||||
soi.make_session(path)
|
|
||||||
|
|
||||||
|
|
||||||
class TestFindDetection(unittest.TestCase):
|
|
||||||
def _session_with_response(self, payload):
|
|
||||||
session = MagicMock()
|
|
||||||
response = MagicMock()
|
|
||||||
response.json.return_value = payload
|
|
||||||
response.raise_for_status.return_value = None
|
|
||||||
session.get.return_value = response
|
|
||||||
return session
|
|
||||||
|
|
||||||
def test_found(self):
|
|
||||||
session = self._session_with_response({"hits": {"hits": [{
|
|
||||||
"_id": "abc", "_index": "so-detection",
|
|
||||||
"_source": {"so_detection": {"overrides": [{"type": "suppress"}]}},
|
|
||||||
}]}})
|
|
||||||
doc_id, idx, existing = soi.find_detection(session, "so-detection", "2049201", "suricata")
|
|
||||||
self.assertEqual(doc_id, "abc")
|
|
||||||
self.assertEqual(idx, "so-detection")
|
|
||||||
self.assertEqual(len(existing), 1)
|
|
||||||
|
|
||||||
def test_not_found(self):
|
|
||||||
session = self._session_with_response({"hits": {"hits": []}})
|
|
||||||
doc_id, idx, existing = soi.find_detection(session, "so-detection", "x", "suricata")
|
|
||||||
self.assertIsNone(doc_id)
|
|
||||||
self.assertIsNone(idx)
|
|
||||||
self.assertIsNone(existing)
|
|
||||||
|
|
||||||
def test_no_overrides_field(self):
|
|
||||||
session = self._session_with_response({"hits": {"hits": [{
|
|
||||||
"_id": "abc", "_index": "so-detection",
|
|
||||||
"_source": {"so_detection": {}},
|
|
||||||
}]}})
|
|
||||||
_, _, existing = soi.find_detection(session, "so-detection", "x", "suricata")
|
|
||||||
self.assertEqual(existing, [])
|
|
||||||
|
|
||||||
def test_multiple_hits_warns(self):
|
|
||||||
session = self._session_with_response({"hits": {"hits": [
|
|
||||||
{"_id": "a", "_index": "i", "_source": {"so_detection": {"overrides": []}}},
|
|
||||||
{"_id": "b", "_index": "i", "_source": {"so_detection": {"overrides": []}}},
|
|
||||||
]}})
|
|
||||||
with patch("sys.stdout", new=StringIO()) as out:
|
|
||||||
doc_id, _, _ = soi.find_detection(session, "i", "x", "suricata")
|
|
||||||
self.assertEqual(doc_id, "a")
|
|
||||||
self.assertIn("WARN", out.getvalue())
|
|
||||||
|
|
||||||
|
|
||||||
class TestUpdateOverrides(unittest.TestCase):
|
|
||||||
def test_posts_to_update_endpoint(self):
|
|
||||||
session = MagicMock()
|
|
||||||
response = MagicMock()
|
|
||||||
response.raise_for_status.return_value = None
|
|
||||||
response.json.return_value = {"result": "updated"}
|
|
||||||
session.post.return_value = response
|
|
||||||
|
|
||||||
result = soi.update_overrides(session, "so-detection", "abc", [{"type": "suppress"}])
|
|
||||||
|
|
||||||
self.assertEqual(result, {"result": "updated"})
|
|
||||||
url = session.post.call_args[0][0]
|
|
||||||
self.assertIn("/_update/abc", url)
|
|
||||||
body = session.post.call_args[1]["json"]
|
|
||||||
self.assertEqual(body["doc"]["so_detection"]["overrides"], [{"type": "suppress"}])
|
|
||||||
|
|
||||||
|
|
||||||
class TestConfirmProceed(unittest.TestCase):
|
|
||||||
def test_dry_run_skips_prompt(self):
|
|
||||||
args = MagicMock(dry_run=True)
|
|
||||||
with patch("sys.stdout", new=StringIO()):
|
|
||||||
self.assertTrue(soi.confirm_proceed(args))
|
|
||||||
|
|
||||||
def test_yes_input(self):
|
|
||||||
args = MagicMock(dry_run=False)
|
|
||||||
with patch("sys.stdout", new=StringIO()):
|
|
||||||
with patch("builtins.input", return_value="yes"):
|
|
||||||
self.assertTrue(soi.confirm_proceed(args))
|
|
||||||
|
|
||||||
def test_yes_input_case_insensitive(self):
|
|
||||||
args = MagicMock(dry_run=False)
|
|
||||||
with patch("sys.stdout", new=StringIO()):
|
|
||||||
with patch("builtins.input", return_value="YES"):
|
|
||||||
self.assertTrue(soi.confirm_proceed(args))
|
|
||||||
|
|
||||||
def test_no_input_aborts(self):
|
|
||||||
args = MagicMock(dry_run=False)
|
|
||||||
with patch("sys.stdout", new=StringIO()):
|
|
||||||
with patch("builtins.input", return_value="no"):
|
|
||||||
self.assertFalse(soi.confirm_proceed(args))
|
|
||||||
|
|
||||||
def test_empty_input_aborts(self):
|
|
||||||
args = MagicMock(dry_run=False)
|
|
||||||
with patch("sys.stdout", new=StringIO()):
|
|
||||||
with patch("builtins.input", return_value=""):
|
|
||||||
self.assertFalse(soi.confirm_proceed(args))
|
|
||||||
|
|
||||||
|
|
||||||
class TestParseArgs(unittest.TestCase):
|
|
||||||
def test_defaults(self):
|
|
||||||
with patch.object(sys, "argv", ["cmd", "--source", "/some/path"]):
|
|
||||||
args = soi.parse_args()
|
|
||||||
self.assertEqual(args.source, "/some/path")
|
|
||||||
self.assertEqual(args.engine, "suricata")
|
|
||||||
self.assertFalse(args.dry_run)
|
|
||||||
self.assertFalse(args.no_import_note)
|
|
||||||
self.assertEqual(args.index, soi.DEFAULT_INDEX)
|
|
||||||
|
|
||||||
def test_all_options(self):
|
|
||||||
argv = ["cmd", "-s", "/x", "-e", "suricata", "-n",
|
|
||||||
"--no-import-note", "-i", "alt-index"]
|
|
||||||
with patch.object(sys, "argv", argv):
|
|
||||||
args = soi.parse_args()
|
|
||||||
self.assertEqual(args.source, "/x")
|
|
||||||
self.assertTrue(args.dry_run)
|
|
||||||
self.assertTrue(args.no_import_note)
|
|
||||||
self.assertEqual(args.index, "alt-index")
|
|
||||||
|
|
||||||
|
|
||||||
class TestMain(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.tmpdir = tempfile.mkdtemp()
|
|
||||||
self.addCleanup(shutil.rmtree, self.tmpdir, ignore_errors=True)
|
|
||||||
# Stub make_session so tests don't need /opt/so/conf/elasticsearch/curl.config.
|
|
||||||
p = patch.object(soi, "make_session", return_value=MagicMock())
|
|
||||||
p.start()
|
|
||||||
self.addCleanup(p.stop)
|
|
||||||
|
|
||||||
def _write_file(self, public_id, overrides, ext="txt"):
|
|
||||||
"""Write an NDJSON override file. Entries may be dicts or raw strings (for malformed input)."""
|
|
||||||
path = os.path.join(self.tmpdir, f"{public_id}.{ext}")
|
|
||||||
with open(path, "w") as f:
|
|
||||||
for o in overrides:
|
|
||||||
f.write(o if isinstance(o, str) else json.dumps(o))
|
|
||||||
f.write("\n")
|
|
||||||
return path
|
|
||||||
|
|
||||||
def _run_main(self, *extra_argv, input_response="yes"):
|
|
||||||
"""Run main() with stdout/stderr captured and input mocked. Returns (stdout, stderr, exit_code)."""
|
|
||||||
argv = ["cmd", "--source", self.tmpdir, *extra_argv]
|
|
||||||
out, err = StringIO(), StringIO()
|
|
||||||
with patch.object(sys, "argv", argv), \
|
|
||||||
patch("sys.stdout", new=out), \
|
|
||||||
patch("sys.stderr", new=err), \
|
|
||||||
patch("builtins.input", return_value=input_response):
|
|
||||||
with self.assertRaises(SystemExit) as cm:
|
|
||||||
soi.main()
|
|
||||||
return out.getvalue(), err.getvalue(), cm.exception.code
|
|
||||||
|
|
||||||
def test_source_dir_missing(self):
|
|
||||||
argv = ["cmd", "--source", "/no/such/path/here"]
|
|
||||||
err = StringIO()
|
|
||||||
with patch.object(sys, "argv", argv), patch("sys.stderr", new=err):
|
|
||||||
with self.assertRaises(SystemExit) as cm:
|
|
||||||
soi.main()
|
|
||||||
self.assertEqual(cm.exception.code, 1)
|
|
||||||
self.assertIn("source directory not found", err.getvalue())
|
|
||||||
|
|
||||||
def test_no_files_found(self):
|
|
||||||
out, _, code = self._run_main()
|
|
||||||
self.assertEqual(code, 0)
|
|
||||||
self.assertIn("No *.txt files found", out)
|
|
||||||
|
|
||||||
def test_user_aborts(self):
|
|
||||||
self._write_file("1001", [{"type": "suppress", "track": "by_src", "ip": "1.2.3.4"}])
|
|
||||||
out, _, code = self._run_main(input_response="no")
|
|
||||||
self.assertEqual(code, 1)
|
|
||||||
self.assertIn("Aborted", out)
|
|
||||||
|
|
||||||
def test_parse_error_increments_error(self):
|
|
||||||
# Malformed JSON line — parse_overrides_file raises JSONDecodeError.
|
|
||||||
self._write_file("1002", ["not json"])
|
|
||||||
out, _, code = self._run_main("--dry-run")
|
|
||||||
self.assertEqual(code, 1) # invalid+error → non-zero
|
|
||||||
self.assertIn("could not parse", out)
|
|
||||||
self.assertIn("Errors: 1", out)
|
|
||||||
|
|
||||||
def test_empty_file_skipped(self):
|
|
||||||
# Blank lines only — parse_overrides_file returns []; main reports "empty file" and continues.
|
|
||||||
path = os.path.join(self.tmpdir, "1003.txt")
|
|
||||||
with open(path, "w") as f:
|
|
||||||
f.write("\n\n")
|
|
||||||
out, _, code = self._run_main("--dry-run")
|
|
||||||
self.assertEqual(code, 0)
|
|
||||||
self.assertIn("empty file", out)
|
|
||||||
|
|
||||||
@patch.object(soi, "find_detection")
|
|
||||||
def test_search_http_error(self, mock_find):
|
|
||||||
mock_find.side_effect = requests.HTTPError("boom")
|
|
||||||
self._write_file("1004", [{"type": "suppress", "track": "by_src", "ip": "1.2.3.4"}])
|
|
||||||
out, _, code = self._run_main("--dry-run")
|
|
||||||
self.assertEqual(code, 1)
|
|
||||||
self.assertIn("search failed", out)
|
|
||||||
|
|
||||||
@patch.object(soi, "find_detection")
|
|
||||||
def test_no_detection_found(self, mock_find):
|
|
||||||
mock_find.return_value = (None, None, None)
|
|
||||||
self._write_file("1005", [{"type": "suppress", "track": "by_src", "ip": "1.2.3.4"}])
|
|
||||||
out, _, code = self._run_main("--dry-run")
|
|
||||||
self.assertEqual(code, 0)
|
|
||||||
self.assertIn("no detection found", out)
|
|
||||||
self.assertIn("Skipped (no detection): 1", out)
|
|
||||||
|
|
||||||
@patch.object(soi, "find_detection")
|
|
||||||
def test_all_duplicates_no_update(self, mock_find):
|
|
||||||
existing = [{"type": "suppress", "track": "by_src", "ip": "1.2.3.4"}]
|
|
||||||
mock_find.return_value = ("doc1", "so-detection", existing)
|
|
||||||
self._write_file("1006", [{"type": "suppress", "track": "by_src", "ip": "1.2.3.4"}])
|
|
||||||
out, _, code = self._run_main("--dry-run")
|
|
||||||
self.assertEqual(code, 0)
|
|
||||||
self.assertIn("SKIP", out)
|
|
||||||
self.assertNotIn("DRY-RUN: would update", out) # added_this_file == 0 branch
|
|
||||||
|
|
||||||
@patch.object(soi, "update_overrides")
|
|
||||||
@patch.object(soi, "find_detection")
|
|
||||||
def test_happy_path_full(self, mock_find, mock_update):
|
|
||||||
# Exercises: ADD, dedupe SKIP, INVALID, note prefix, UPDATE, custom-vars warning, exit=1 (invalid present)
|
|
||||||
existing = [{"type": "suppress", "track": "by_src", "ip": "9.9.9.9"}]
|
|
||||||
mock_find.return_value = ("doc1", "so-detection", existing)
|
|
||||||
mock_update.return_value = {"result": "updated"}
|
|
||||||
self._write_file("1007", [
|
|
||||||
{"type": "suppress", "track": "by_src", "ip": "1.2.3.4"}, # ADD
|
|
||||||
{"type": "suppress", "track": "by_src", "ip": "9.9.9.9"}, # SKIP (dupe of existing)
|
|
||||||
{"type": "suppress", "track": "bogus", "ip": "1.2.3.4"}, # INVALID
|
|
||||||
{"type": "suppress", "track": "by_src", "ip": "$CONCOURSEWORKERS"}, # ADD + custom var
|
|
||||||
])
|
|
||||||
out, _, code = self._run_main()
|
|
||||||
self.assertEqual(code, 1) # one invalid -> non-zero
|
|
||||||
|
|
||||||
mock_update.assert_called_once()
|
|
||||||
merged = mock_update.call_args[0][3]
|
|
||||||
self.assertEqual(len(merged), 3) # 1 existing + 2 new
|
|
||||||
new_notes = [o.get("note", "") for o in merged if o.get("ip") in ("1.2.3.4", "$CONCOURSEWORKERS")]
|
|
||||||
self.assertTrue(all(n.startswith("[Imported ") for n in new_notes))
|
|
||||||
|
|
||||||
self.assertIn("ADD", out)
|
|
||||||
self.assertIn("SKIP", out)
|
|
||||||
self.assertIn("INVALID", out)
|
|
||||||
self.assertIn("UPDATED", out)
|
|
||||||
self.assertIn("$CONCOURSEWORKERS", out)
|
|
||||||
|
|
||||||
@patch.object(soi, "update_overrides")
|
|
||||||
@patch.object(soi, "find_detection")
|
|
||||||
def test_no_import_note_preserves_note(self, mock_find, mock_update):
|
|
||||||
mock_find.return_value = ("doc1", "so-detection", [])
|
|
||||||
mock_update.return_value = {"result": "updated"}
|
|
||||||
self._write_file("1008", [
|
|
||||||
{"type": "suppress", "track": "by_src", "ip": "1.2.3.4", "note": "original"},
|
|
||||||
])
|
|
||||||
_, _, code = self._run_main("--no-import-note")
|
|
||||||
self.assertEqual(code, 0)
|
|
||||||
merged = mock_update.call_args[0][3]
|
|
||||||
self.assertEqual(merged[0]["note"], "original") # no prefix applied
|
|
||||||
|
|
||||||
@patch.object(soi, "find_detection")
|
|
||||||
def test_dry_run_skips_update(self, mock_find):
|
|
||||||
mock_find.return_value = ("doc1", "so-detection", [])
|
|
||||||
self._write_file("1009", [{"type": "suppress", "track": "by_src", "ip": "1.2.3.4"}])
|
|
||||||
with patch.object(soi, "update_overrides") as mock_update:
|
|
||||||
out, _, code = self._run_main("--dry-run")
|
|
||||||
self.assertEqual(code, 0)
|
|
||||||
mock_update.assert_not_called()
|
|
||||||
self.assertIn("DRY-RUN: would update", out)
|
|
||||||
|
|
||||||
@patch.object(soi, "update_overrides")
|
|
||||||
@patch.object(soi, "find_detection")
|
|
||||||
def test_update_http_error(self, mock_find, mock_update):
|
|
||||||
mock_find.return_value = ("doc1", "so-detection", [])
|
|
||||||
mock_update.side_effect = requests.HTTPError("nope")
|
|
||||||
self._write_file("1010", [{"type": "suppress", "track": "by_src", "ip": "1.2.3.4"}])
|
|
||||||
out, _, code = self._run_main()
|
|
||||||
self.assertEqual(code, 1)
|
|
||||||
self.assertIn("update failed", out)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest.main()
|
|
||||||
@@ -132,8 +132,8 @@ function getinstallinfo() {
|
|||||||
log "ERROR" "Failed to get install info from $MINION_ID"
|
log "ERROR" "Failed to get install info from $MINION_ID"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
while read -r var; do export "$var"; done <<< "$INSTALLVARS"
|
export $(echo "$INSTALLVARS" | xargs)
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
log "ERROR" "Failed to source install variables"
|
log "ERROR" "Failed to source install variables"
|
||||||
return 1
|
return 1
|
||||||
@@ -273,7 +273,7 @@ function deleteMinionFiles () {
|
|||||||
log "ERROR" "Failed to delete $PILLARFILE"
|
log "ERROR" "Failed to delete $PILLARFILE"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f $ADVPILLARFILE
|
rm -f $ADVPILLARFILE
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
log "ERROR" "Failed to delete $ADVPILLARFILE"
|
log "ERROR" "Failed to delete $ADVPILLARFILE"
|
||||||
@@ -281,39 +281,6 @@ function deleteMinionFiles () {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Remove this minion's postgres Telegraf credential from the shared creds
|
|
||||||
# pillar and drop the matching role in Postgres. Always returns 0 so a dead
|
|
||||||
# or unreachable so-postgres doesn't block minion deletion — in that case we
|
|
||||||
# log a warning and leave the role behind for manual cleanup.
|
|
||||||
function remove_postgres_telegraf_from_minion() {
|
|
||||||
local MINION_SAFE
|
|
||||||
MINION_SAFE=$(echo "$MINION_ID" | tr '.-' '__' | tr '[:upper:]' '[:lower:]')
|
|
||||||
local PG_USER="so_telegraf_${MINION_SAFE}"
|
|
||||||
|
|
||||||
log "INFO" "Removing postgres telegraf cred for $MINION_ID"
|
|
||||||
|
|
||||||
so-telegraf-cred remove "$MINION_ID" >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q '^so-postgres$'; then
|
|
||||||
if ! docker exec -i so-postgres psql -v ON_ERROR_STOP=1 -U postgres -d so_telegraf >/dev/null 2>&1 <<EOSQL
|
|
||||||
DO \$\$
|
|
||||||
BEGIN
|
|
||||||
IF EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = '$PG_USER') THEN
|
|
||||||
EXECUTE format('REASSIGN OWNED BY %I TO so_telegraf', '$PG_USER');
|
|
||||||
EXECUTE format('DROP OWNED BY %I', '$PG_USER');
|
|
||||||
EXECUTE format('DROP ROLE %I', '$PG_USER');
|
|
||||||
END IF;
|
|
||||||
END
|
|
||||||
\$\$;
|
|
||||||
EOSQL
|
|
||||||
then
|
|
||||||
log "WARN" "Failed to drop postgres role $PG_USER; pillar entry was removed — drop manually if the role persists"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
log "WARN" "so-postgres container is not running; skipping DB role cleanup for $PG_USER"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create the minion file
|
# Create the minion file
|
||||||
function ensure_socore_ownership() {
|
function ensure_socore_ownership() {
|
||||||
log "INFO" "Setting socore ownership on minion files"
|
log "INFO" "Setting socore ownership on minion files"
|
||||||
@@ -544,28 +511,6 @@ function add_redis_to_minion() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
function add_strelka_to_minion() {
|
|
||||||
printf '%s\n'\
|
|
||||||
"strelka:"\
|
|
||||||
" backend:"\
|
|
||||||
" enabled: True"\
|
|
||||||
" filestream:"\
|
|
||||||
" enabled: True"\
|
|
||||||
" frontend:"\
|
|
||||||
" enabled: True"\
|
|
||||||
" manager:"\
|
|
||||||
" enabled: True"\
|
|
||||||
" coordinator:"\
|
|
||||||
" enabled: True"\
|
|
||||||
" gatekeeper:"\
|
|
||||||
" enabled: True"\
|
|
||||||
" " >> $PILLARFILE
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
log "ERROR" "Failed to add strelka configuration to $PILLARFILE"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
function add_telegraf_to_minion() {
|
function add_telegraf_to_minion() {
|
||||||
printf '%s\n'\
|
printf '%s\n'\
|
||||||
"telegraf:"\
|
"telegraf:"\
|
||||||
@@ -575,17 +520,6 @@ function add_telegraf_to_minion() {
|
|||||||
log "ERROR" "Failed to add telegraf configuration to $PILLARFILE"
|
log "ERROR" "Failed to add telegraf configuration to $PILLARFILE"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Provision the per-minion postgres Telegraf credential in the shared
|
|
||||||
# telegraf/creds.sls pillar. so-telegraf-cred is the only writer; it
|
|
||||||
# generates a password on first add and is a no-op on re-add so the cred
|
|
||||||
# is stable across repeated so-minion runs. postgres.telegraf_users on the
|
|
||||||
# manager creates/updates the DB role from the same pillar.
|
|
||||||
so-telegraf-cred add "$MINION_ID"
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
log "ERROR" "Failed to provision postgres telegraf cred for $MINION_ID"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function add_influxdb_to_minion() {
|
function add_influxdb_to_minion() {
|
||||||
@@ -773,7 +707,6 @@ function createEVAL() {
|
|||||||
pcapspace || return 1
|
pcapspace || return 1
|
||||||
add_elasticsearch_to_minion || return 1
|
add_elasticsearch_to_minion || return 1
|
||||||
add_sensor_to_minion || return 1
|
add_sensor_to_minion || return 1
|
||||||
add_strelka_to_minion || return 1
|
|
||||||
add_elastalert_to_minion || return 1
|
add_elastalert_to_minion || return 1
|
||||||
add_kibana_to_minion || return 1
|
add_kibana_to_minion || return 1
|
||||||
add_telegraf_to_minion || return 1
|
add_telegraf_to_minion || return 1
|
||||||
@@ -792,7 +725,6 @@ function createSTANDALONE() {
|
|||||||
add_elasticsearch_to_minion || return 1
|
add_elasticsearch_to_minion || return 1
|
||||||
add_logstash_to_minion || return 1
|
add_logstash_to_minion || return 1
|
||||||
add_sensor_to_minion || return 1
|
add_sensor_to_minion || return 1
|
||||||
add_strelka_to_minion || return 1
|
|
||||||
add_elastalert_to_minion || return 1
|
add_elastalert_to_minion || return 1
|
||||||
add_kibana_to_minion || return 1
|
add_kibana_to_minion || return 1
|
||||||
add_redis_to_minion || return 1
|
add_redis_to_minion || return 1
|
||||||
@@ -877,7 +809,6 @@ function createHEAVYNODE() {
|
|||||||
add_elasticsearch_to_minion || return 1
|
add_elasticsearch_to_minion || return 1
|
||||||
add_elastic_agent_to_minion || return 1
|
add_elastic_agent_to_minion || return 1
|
||||||
add_sensor_to_minion || return 1
|
add_sensor_to_minion || return 1
|
||||||
add_strelka_to_minion || return 1
|
|
||||||
add_telegraf_to_minion || return 1
|
add_telegraf_to_minion || return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -888,7 +819,6 @@ function createSENSOR() {
|
|||||||
PCAP_PERCENTAGE=3
|
PCAP_PERCENTAGE=3
|
||||||
pcapspace || return 1
|
pcapspace || return 1
|
||||||
add_sensor_to_minion || return 1
|
add_sensor_to_minion || return 1
|
||||||
add_strelka_to_minion || return 1
|
|
||||||
add_telegraf_to_minion || return 1
|
add_telegraf_to_minion || return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1113,7 +1043,6 @@ case "$OPERATION" in
|
|||||||
|
|
||||||
"delete")
|
"delete")
|
||||||
log "INFO" "Removing minion $MINION_ID"
|
log "INFO" "Removing minion $MINION_ID"
|
||||||
remove_postgres_telegraf_from_minion
|
|
||||||
deleteMinionFiles || {
|
deleteMinionFiles || {
|
||||||
log "ERROR" "Failed to delete minion files for $MINION_ID"
|
log "ERROR" "Failed to delete minion files for $MINION_ID"
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
|
|
||||||
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
|
|
||||||
# https://securityonion.net/license; you may not use this file except in compliance with the
|
|
||||||
# Elastic License 2.0.
|
|
||||||
|
|
||||||
# Single writer for the Telegraf Postgres credentials pillar. Thin wrapper
|
|
||||||
# around so-yaml.py that generates a password on first add and no-ops on
|
|
||||||
# re-add so the cred is stable across repeated so-minion runs.
|
|
||||||
#
|
|
||||||
# Note: so-yaml.py splits keys on '.' with no escape. SO minion ids are
|
|
||||||
# dot-free by construction (setup/so-functions:1884 takes the short_name
|
|
||||||
# before the first '.'), so using the raw minion id as the key is safe.
|
|
||||||
|
|
||||||
CREDS=/opt/so/saltstack/local/pillar/telegraf/creds.sls
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
echo "Usage: $0 <add|remove> <minion_id>" >&2
|
|
||||||
exit 2
|
|
||||||
}
|
|
||||||
|
|
||||||
seed_creds_file() {
|
|
||||||
mkdir -p "$(dirname "$CREDS")" || return 1
|
|
||||||
if [[ ! -f "$CREDS" ]]; then
|
|
||||||
(umask 027 && printf 'telegraf:\n postgres_creds: {}\n' > "$CREDS") || return 1
|
|
||||||
chown socore:socore "$CREDS" 2>/dev/null || true
|
|
||||||
chmod 640 "$CREDS" || return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
OP=$1
|
|
||||||
MID=$2
|
|
||||||
[[ -z "$OP" || -z "$MID" ]] && usage
|
|
||||||
|
|
||||||
case "$OP" in
|
|
||||||
add)
|
|
||||||
SAFE=$(echo "$MID" | tr '.-' '__' | tr '[:upper:]' '[:lower:]')
|
|
||||||
seed_creds_file || exit 1
|
|
||||||
if so-yaml.py get -r "$CREDS" "telegraf.postgres_creds.${MID}.user" >/dev/null 2>&1; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
PASS=$(tr -dc 'A-Za-z0-9~!@#^&*()_=+[]|;:,.<>?-' < /dev/urandom | head -c 72)
|
|
||||||
so-yaml.py replace "$CREDS" "telegraf.postgres_creds.${MID}.user" "so_telegraf_${SAFE}" >/dev/null
|
|
||||||
so-yaml.py replace "$CREDS" "telegraf.postgres_creds.${MID}.pass" "$PASS" >/dev/null
|
|
||||||
;;
|
|
||||||
remove)
|
|
||||||
[[ -f "$CREDS" ]] || exit 0
|
|
||||||
so-yaml.py remove "$CREDS" "telegraf.postgres_creds.${MID}" >/dev/null 2>&1 || true
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
usage
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
@@ -39,16 +39,9 @@ def showUsage(args):
|
|||||||
|
|
||||||
|
|
||||||
def loadYaml(filename):
|
def loadYaml(filename):
|
||||||
try:
|
file = open(filename, "r")
|
||||||
with open(filename, "r") as file:
|
content = file.read()
|
||||||
content = file.read()
|
return yaml.safe_load(content)
|
||||||
return yaml.safe_load(content)
|
|
||||||
except FileNotFoundError:
|
|
||||||
print(f"File not found: {filename}", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error reading file {filename}: {e}", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
def writeYaml(filename, content):
|
def writeYaml(filename, content):
|
||||||
@@ -292,8 +285,7 @@ def add(args):
|
|||||||
def removeKey(content, key):
|
def removeKey(content, key):
|
||||||
pieces = key.split(".", 1)
|
pieces = key.split(".", 1)
|
||||||
if len(pieces) > 1:
|
if len(pieces) > 1:
|
||||||
if pieces[0] in content:
|
removeKey(content[pieces[0]], pieces[1])
|
||||||
removeKey(content[pieces[0]], pieces[1])
|
|
||||||
else:
|
else:
|
||||||
content.pop(key, None)
|
content.pop(key, None)
|
||||||
|
|
||||||
|
|||||||
@@ -973,21 +973,3 @@ class TestReplaceListObject(unittest.TestCase):
|
|||||||
|
|
||||||
expected = "key1:\n- id: '1'\n status: updated\n- id: '2'\n status: inactive\n"
|
expected = "key1:\n- id: '1'\n status: updated\n- id: '2'\n status: inactive\n"
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
|
||||||
class TestLoadYaml(unittest.TestCase):
|
|
||||||
|
|
||||||
def test_load_yaml_missing_file(self):
|
|
||||||
with patch('sys.exit', new=MagicMock()) as sysmock:
|
|
||||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
|
||||||
soyaml.loadYaml("/tmp/so-yaml_test-does-not-exist.yaml")
|
|
||||||
sysmock.assert_called_with(1)
|
|
||||||
self.assertIn("File not found:", mock_stderr.getvalue())
|
|
||||||
|
|
||||||
def test_load_yaml_read_error(self):
|
|
||||||
with patch('sys.exit', new=MagicMock()) as sysmock:
|
|
||||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
|
||||||
with patch('builtins.open', side_effect=PermissionError("denied")):
|
|
||||||
soyaml.loadYaml("/tmp/so-yaml_test-unreadable.yaml")
|
|
||||||
sysmock.assert_called_with(1)
|
|
||||||
self.assertIn("Error reading file", mock_stderr.getvalue())
|
|
||||||
|
|||||||
+65
-546
@@ -24,14 +24,6 @@ BACKUPTOPFILE=/opt/so/saltstack/default/salt/top.sls.backup
|
|||||||
SALTUPGRADED=false
|
SALTUPGRADED=false
|
||||||
SALT_CLOUD_INSTALLED=false
|
SALT_CLOUD_INSTALLED=false
|
||||||
SALT_CLOUD_CONFIGURED=false
|
SALT_CLOUD_CONFIGURED=false
|
||||||
# Check if salt-cloud is installed
|
|
||||||
if rpm -q salt-cloud &>/dev/null; then
|
|
||||||
SALT_CLOUD_INSTALLED=true
|
|
||||||
fi
|
|
||||||
# Check if salt-cloud is configured
|
|
||||||
if [[ -f /etc/salt/cloud.profiles.d/socloud.conf ]]; then
|
|
||||||
SALT_CLOUD_CONFIGURED=true
|
|
||||||
fi
|
|
||||||
# used to display messages to the user at the end of soup
|
# used to display messages to the user at the end of soup
|
||||||
declare -a FINAL_MESSAGE_QUEUE=()
|
declare -a FINAL_MESSAGE_QUEUE=()
|
||||||
|
|
||||||
@@ -188,6 +180,13 @@ 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
|
||||||
@@ -343,11 +342,10 @@ highstate() {
|
|||||||
masterlock() {
|
masterlock() {
|
||||||
echo "Locking Salt Master"
|
echo "Locking Salt Master"
|
||||||
mv -v $TOPFILE $BACKUPTOPFILE
|
mv -v $TOPFILE $BACKUPTOPFILE
|
||||||
# Render the real top file only for the host running soup; every other
|
echo "base:" > $TOPFILE
|
||||||
# minion gets an empty top (no states) while the master is upgrading.
|
echo " $MINIONID:" >> $TOPFILE
|
||||||
echo "{% if grains['id'] == '$MINIONID' %}" > $TOPFILE
|
echo " - ca" >> $TOPFILE
|
||||||
cat $BACKUPTOPFILE >> $TOPFILE
|
echo " - elasticsearch" >> $TOPFILE
|
||||||
echo "{% endif %}" >> $TOPFILE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
masterunlock() {
|
masterunlock() {
|
||||||
@@ -364,9 +362,7 @@ preupgrade_changes() {
|
|||||||
# This function is to add any new pillar items if needed.
|
# This function is to add any new pillar items if needed.
|
||||||
echo "Checking to see if changes are needed."
|
echo "Checking to see if changes are needed."
|
||||||
|
|
||||||
[[ "$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.1.0" ]] && up_to_3.2.0
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,8 +371,6 @@ postupgrade_changes() {
|
|||||||
echo "Running post upgrade processes."
|
echo "Running post upgrade processes."
|
||||||
|
|
||||||
[[ "$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.1.0" ]] && post_to_3.2.0
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,6 +445,7 @@ migrate_pcap_to_suricata() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
up_to_3.0.0() {
|
up_to_3.0.0() {
|
||||||
|
determine_elastic_agent_upgrade
|
||||||
migrate_pcap_to_suricata
|
migrate_pcap_to_suricata
|
||||||
|
|
||||||
INSTALLEDVERSION=3.0.0
|
INSTALLEDVERSION=3.0.0
|
||||||
@@ -474,313 +469,6 @@ post_to_3.0.0() {
|
|||||||
|
|
||||||
### 3.0.0 End ###
|
### 3.0.0 End ###
|
||||||
|
|
||||||
### 3.1.0 Scripts ###
|
|
||||||
|
|
||||||
elasticsearch_backup_index_templates() {
|
|
||||||
echo "Backing up current elasticsearch index templates in /opt/so/conf/elasticsearch/templates/index/ to /nsm/backup/3.0.0_elasticsearch_index_templates.tar.gz"
|
|
||||||
tar -czf /nsm/backup/3.0.0_elasticsearch_index_templates.tar.gz -C /opt/so/conf/elasticsearch/templates/index/ .
|
|
||||||
}
|
|
||||||
|
|
||||||
elasticfleet_set_agent_logging_level_warn() {
|
|
||||||
. /usr/sbin/so-elastic-fleet-common
|
|
||||||
|
|
||||||
local current_agent_policies
|
|
||||||
if ! current_agent_policies=$(fleet_api "agent_policies?perPage=1000"); then
|
|
||||||
echo "Warning: unable to retrieve Fleet agent policies"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Only updating policies that are within Security Onion defaults and do not already have any user configured advanced_settings.
|
|
||||||
local policies_to_update
|
|
||||||
policies_to_update=$(jq -c '
|
|
||||||
.items[]
|
|
||||||
| select(has("advanced_settings") | not)
|
|
||||||
| select(
|
|
||||||
.id == "so-grid-nodes_general"
|
|
||||||
or .id == "so-grid-nodes_heavy"
|
|
||||||
or .id == "endpoints-initial"
|
|
||||||
or (.id | startswith("FleetServer_"))
|
|
||||||
)
|
|
||||||
' <<< "$current_agent_policies")
|
|
||||||
|
|
||||||
if [[ -z "$policies_to_update" ]]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
while IFS= read -r policy; do
|
|
||||||
[[ -z "$policy" ]] && continue
|
|
||||||
|
|
||||||
local policy_id policy_name policy_namespace
|
|
||||||
policy_id=$(jq -r '.id' <<< "$policy")
|
|
||||||
policy_name=$(jq -r '.name' <<< "$policy")
|
|
||||||
policy_namespace=$(jq -r '.namespace' <<< "$policy")
|
|
||||||
|
|
||||||
local update_logging
|
|
||||||
update_logging=$(jq -n \
|
|
||||||
--arg name "$policy_name" \
|
|
||||||
--arg namespace "$policy_namespace" \
|
|
||||||
'{name: $name, namespace: $namespace, advanced_settings: {agent_logging_level: "warning"}}'
|
|
||||||
)
|
|
||||||
|
|
||||||
echo "Setting elastic agent_logging_level to warning on policy '$policy_name' ($policy_id)."
|
|
||||||
if ! fleet_api "agent_policies/$policy_id" -XPUT -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$update_logging" >/dev/null; then
|
|
||||||
echo " warning: failed to update agent policy '$policy_name' ($policy_id)" >&2
|
|
||||||
fi
|
|
||||||
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() {
|
|
||||||
. /usr/sbin/so-elastic-fleet-common
|
|
||||||
|
|
||||||
echo "Checking integration transform jobs for unhealthy / unauthorized status..."
|
|
||||||
|
|
||||||
local transforms_doc stats_doc installed_doc
|
|
||||||
if ! transforms_doc=$(so-elasticsearch-query "_transform/_all?size=1000" --fail --retry 3 --retry-delay 5 2>/dev/null); then
|
|
||||||
echo "Unable to query for transform jobs, skipping reauthorization."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if ! stats_doc=$(so-elasticsearch-query "_transform/_all/_stats?size=1000" --fail --retry 3 --retry-delay 5 2>/dev/null); then
|
|
||||||
echo "Unable to query for transform job stats, skipping reauthorization."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if ! installed_doc=$(fleet_api "epm/packages/installed?perPage=500"); then
|
|
||||||
echo "Unable to list installed Fleet packages, skipping reauthorization."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Get all transforms that meet the following
|
|
||||||
# - unhealthy (any non-green health status)
|
|
||||||
# - metadata has run_as_kibana_system: false (this fix is specific to transforms started prior to Kibana 9.3.3)
|
|
||||||
# - are not orphaned (integration is not somehow missing/corrupt/uninstalled)
|
|
||||||
local tmp_transforms tmp_stats tmp_installed
|
|
||||||
tmp_transforms=$(mktemp)
|
|
||||||
tmp_stats=$(mktemp)
|
|
||||||
tmp_installed=$(mktemp)
|
|
||||||
|
|
||||||
echo "$transforms_doc" > "$tmp_transforms"
|
|
||||||
echo "$stats_doc" > "$tmp_stats"
|
|
||||||
echo "$installed_doc" > "$tmp_installed"
|
|
||||||
|
|
||||||
local unhealthy_transforms
|
|
||||||
unhealthy_transforms=$(jq -c -n \
|
|
||||||
--slurpfile t "$tmp_transforms" \
|
|
||||||
--slurpfile s "$tmp_stats" \
|
|
||||||
--slurpfile i "$tmp_installed" '
|
|
||||||
($i[0].items | map({key: .name, value: .version}) | from_entries) as $pkg_ver
|
|
||||||
| ($s[0].transforms | map({key: .id, value: .health.status}) | from_entries) as $health
|
|
||||||
| [ $t[0].transforms[]
|
|
||||||
| select(._meta.run_as_kibana_system == false)
|
|
||||||
| select(($health[.id] // "unknown") != "green")
|
|
||||||
| {id, pkg: ._meta.package.name, ver: ($pkg_ver[._meta.package.name])}
|
|
||||||
]
|
|
||||||
| if length == 0 then empty else . end
|
|
||||||
| (map(select(.ver == null)) | map({orphan: .id})[]),
|
|
||||||
(map(select(.ver != null))
|
|
||||||
| group_by(.pkg)
|
|
||||||
| map({pkg: .[0].pkg, ver: .[0].ver, transformIds: map(.id)})[])
|
|
||||||
')
|
|
||||||
|
|
||||||
if [[ -z "$unhealthy_transforms" ]]; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
local unhealthy_count
|
|
||||||
unhealthy_count=$(jq -s '[.[].transformIds? // empty | .[]] | length' <<< "$unhealthy_transforms")
|
|
||||||
echo "Found $unhealthy_count transform(s) needing reauthorization."
|
|
||||||
|
|
||||||
local total_failures=0
|
|
||||||
while IFS= read -r transform; do
|
|
||||||
[[ -z "$transform" ]] && continue
|
|
||||||
if jq -e 'has("orphan")' <<< "$transform" >/dev/null 2>&1; then
|
|
||||||
echo "Skipping transform not owned by any installed Fleet package: $(jq -r '.orphan' <<< "$transform")"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
local pkg ver body resp
|
|
||||||
pkg=$(jq -r '.pkg' <<< "$transform")
|
|
||||||
ver=$(jq -r '.ver' <<< "$transform")
|
|
||||||
body=$(jq -c '{transforms: (.transformIds | map({transformId: .}))}' <<< "$transform")
|
|
||||||
|
|
||||||
echo "Reauthorizing transform(s) for ${pkg}-${ver}..."
|
|
||||||
resp=$(fleet_api "epm/packages/${pkg}/${ver}/transforms/authorize" \
|
|
||||||
-XPOST -H 'kbn-xsrf: true' -H 'Content-Type: application/json' \
|
|
||||||
-d "$body") || { echo "Could not reauthorize transform(s) for ${pkg}-${ver}"; continue; }
|
|
||||||
|
|
||||||
(( total_failures += $(jq 'map(select(.success != true)) | length' <<< "$resp" 2>/dev/null) ))
|
|
||||||
done <<< "$unhealthy_transforms"
|
|
||||||
|
|
||||||
rm -f "$tmp_transforms" "$tmp_stats" "$tmp_installed"
|
|
||||||
|
|
||||||
if [[ "$total_failures" -gt 0 ]]; then
|
|
||||||
echo "Some transform(s) failed to reauthorize."
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_postgres_local_pillar() {
|
|
||||||
# Postgres was added as a service after 3.0.0, so the new pillar/top.sls
|
|
||||||
# references postgres.soc_postgres / postgres.adv_postgres unconditionally.
|
|
||||||
# Managers upgrading from 3.0.0 have no /opt/so/saltstack/local/pillar/postgres/
|
|
||||||
# (make_some_dirs only runs at install time), so the stubs must be created
|
|
||||||
# here before salt-master restarts against the new top.sls.
|
|
||||||
echo "Ensuring postgres local pillar stubs exist."
|
|
||||||
local dir=/opt/so/saltstack/local/pillar/postgres
|
|
||||||
mkdir -p "$dir"
|
|
||||||
[[ -f "$dir/soc_postgres.sls" ]] || touch "$dir/soc_postgres.sls"
|
|
||||||
[[ -f "$dir/adv_postgres.sls" ]] || touch "$dir/adv_postgres.sls"
|
|
||||||
chown -R socore:socore "$dir"
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_postgres_secret() {
|
|
||||||
# On a fresh install, generate_passwords + secrets_pillar seed
|
|
||||||
# secrets:postgres_pass in /opt/so/saltstack/local/pillar/secrets.sls. That
|
|
||||||
# code path is skipped on upgrade (secrets.sls already exists from 3.0.0
|
|
||||||
# with import_pass/influx_pass but no postgres_pass), so the postgres
|
|
||||||
# container's POSTGRES_PASSWORD_FILE and SOC's PG_ADMIN_PASS would be empty
|
|
||||||
# after highstate. Generate one now if missing.
|
|
||||||
local secrets_file=/opt/so/saltstack/local/pillar/secrets.sls
|
|
||||||
if [[ ! -f "$secrets_file" ]]; then
|
|
||||||
echo "WARNING: $secrets_file missing; skipping postgres_pass backfill."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if so-yaml.py get -r "$secrets_file" secrets.postgres_pass >/dev/null 2>&1; then
|
|
||||||
echo "secrets.postgres_pass already set; leaving as-is."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
echo "Seeding secrets.postgres_pass in $secrets_file."
|
|
||||||
so-yaml.py add "$secrets_file" secrets.postgres_pass "$(get_random_value)"
|
|
||||||
chown socore:socore "$secrets_file"
|
|
||||||
}
|
|
||||||
|
|
||||||
rename_strelka_scan_lnk() {
|
|
||||||
echo "Renaming strelka pillar ScanLNK to ScanLnk."
|
|
||||||
local STRELKA_FILE=/opt/so/saltstack/local/pillar/strelka/soc_strelka.sls
|
|
||||||
local MINIONDIR=/opt/so/saltstack/local/pillar/minions
|
|
||||||
local OLD_KEY=strelka.backend.config.backend.scanners.ScanLNK
|
|
||||||
local NEW_KEY=strelka.backend.config.backend.scanners.ScanLnk
|
|
||||||
local TMP_VALUE_FILE
|
|
||||||
TMP_VALUE_FILE=$(mktemp)
|
|
||||||
|
|
||||||
for pillar_file in "$STRELKA_FILE" "$MINIONDIR"/*.sls; do
|
|
||||||
[[ -f "$pillar_file" ]] || continue
|
|
||||||
# Skip if ScanLNK doesn't exist
|
|
||||||
so-yaml.py get "$pillar_file" "$OLD_KEY" > "$TMP_VALUE_FILE" 2>/dev/null || continue
|
|
||||||
echo "Found 'ScanLNK' key in $pillar_file. Renaming to 'ScanLnk'."
|
|
||||||
so-yaml.py add "$pillar_file" "$NEW_KEY" "file:$TMP_VALUE_FILE"
|
|
||||||
so-yaml.py remove "$pillar_file" "$OLD_KEY"
|
|
||||||
done
|
|
||||||
|
|
||||||
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() {
|
|
||||||
ensure_postgres_local_pillar
|
|
||||||
ensure_postgres_secret
|
|
||||||
determine_elastic_agent_upgrade
|
|
||||||
elasticsearch_backup_index_templates
|
|
||||||
# Clear existing component template state file.
|
|
||||||
rm -f /opt/so/state/esfleet_component_templates.json
|
|
||||||
rename_strelka_scan_lnk
|
|
||||||
fix_logstash_0013_lumberjack_pipeline_name
|
|
||||||
|
|
||||||
INSTALLEDVERSION=3.1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
post_to_3.1.0() {
|
|
||||||
/usr/sbin/so-kibana-space-defaults
|
|
||||||
# ensure manager has new version of socloud.conf
|
|
||||||
if [[ $SALT_CLOUD_CONFIGURED == true ]]; then
|
|
||||||
salt-call state.apply salt.cloud.config concurrent=True
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Backfill the Telegraf creds pillar for every accepted minion. so-telegraf-cred
|
|
||||||
# add is idempotent — it no-ops when an entry already exists — so this is safe
|
|
||||||
# to run on every soup. The subsequent state.apply creates/updates the matching
|
|
||||||
# Postgres roles from the reconciled pillar.
|
|
||||||
echo "Reconciling Telegraf Postgres creds for accepted minions."
|
|
||||||
for mid in $(salt-key --out=json --list=accepted 2>/dev/null | jq -r '.minions[]?' 2>/dev/null); do
|
|
||||||
[[ -n "$mid" ]] || continue
|
|
||||||
/usr/sbin/so-telegraf-cred add "$mid" || echo " warning: so-telegraf-cred add $mid failed" >&2
|
|
||||||
done
|
|
||||||
# Run through the master (not --local) so state compilation uses the
|
|
||||||
# master's configured file_roots; the manager's /etc/salt/minion has no
|
|
||||||
# file_roots of its own and --local would fail with "No matching sls found".
|
|
||||||
salt-call state.apply postgres.telegraf_users queue=True || true
|
|
||||||
|
|
||||||
# Update default agent policies to use logging level warn.
|
|
||||||
elasticfleet_set_agent_logging_level_warn || true
|
|
||||||
|
|
||||||
# Check for unhealthy / unauthorized integration transform jobs and attempt reauthorizations
|
|
||||||
check_transform_health_and_reauthorize || true
|
|
||||||
|
|
||||||
POSTVERSION=3.1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
### 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."
|
||||||
su socore -c '/usr/sbin/so-repo-sync' || fail "Unable to complete so-repo-sync."
|
su socore -c '/usr/sbin/so-repo-sync' || fail "Unable to complete so-repo-sync."
|
||||||
@@ -948,6 +636,15 @@ upgrade_check_salt() {
|
|||||||
upgrade_salt() {
|
upgrade_salt() {
|
||||||
echo "Performing upgrade of Salt from $INSTALLEDSALTVERSION to $NEWSALTVERSION."
|
echo "Performing upgrade of Salt from $INSTALLEDSALTVERSION to $NEWSALTVERSION."
|
||||||
echo ""
|
echo ""
|
||||||
|
# Check if salt-cloud is installed
|
||||||
|
if rpm -q salt-cloud &>/dev/null; then
|
||||||
|
SALT_CLOUD_INSTALLED=true
|
||||||
|
fi
|
||||||
|
# Check if salt-cloud is configured
|
||||||
|
if [[ -f /etc/salt/cloud.profiles.d/socloud.conf ]]; then
|
||||||
|
SALT_CLOUD_CONFIGURED=true
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Removing yum versionlock for Salt."
|
echo "Removing yum versionlock for Salt."
|
||||||
echo ""
|
echo ""
|
||||||
yum versionlock delete "salt"
|
yum versionlock delete "salt"
|
||||||
@@ -1031,15 +728,12 @@ 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"
|
["8.18.8"]="9.0.8"
|
||||||
["9.0.8"]="9.3.3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Elasticsearch MUST upgrade through these versions
|
# Elasticsearch MUST upgrade through these versions
|
||||||
declare -A es_to_so_version=(
|
declare -A es_to_so_version=(
|
||||||
["9.0.8"]="3.0.0-20260331"
|
["8.18.8"]="2.4.190-20251024"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get current Elasticsearch version
|
# Get current Elasticsearch version
|
||||||
@@ -1051,182 +745,26 @@ verify_es_version_compatibility() {
|
|||||||
exit 160
|
exit 160
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! target_es_version=$(so-yaml.py get -r $UPDATE_DIR/salt/elasticsearch/defaults.yaml elasticsearch.version); then
|
if ! target_es_version_raw=$(so-yaml.py get $UPDATE_DIR/salt/elasticsearch/defaults.yaml elasticsearch.version); then
|
||||||
echo "Couldn't determine the target Elasticsearch version (post soup version) to ensure compatibility with current Elasticsearch version. Exiting"
|
# so-yaml.py failed to get the ES version from upgrade versions elasticsearch/defaults.yaml file. Likely they are upgrading to an SO version older than 2.4.110 prior to the ES version pinning and should be OKAY to continue with the upgrade.
|
||||||
|
|
||||||
exit 160
|
# if so-yaml.py failed to get the ES version AND the version we are upgrading to is newer than 2.4.110 then we should bail
|
||||||
fi
|
if [[ $(cat $UPDATE_DIR/VERSION | cut -d'.' -f3) > 110 ]]; then
|
||||||
|
echo "Couldn't determine the target Elasticsearch version (post soup version) to ensure compatibility with current Elasticsearch version. Exiting"
|
||||||
|
|
||||||
compatible_es_versions="$target_es_version"
|
exit 160
|
||||||
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
|
fi
|
||||||
|
|
||||||
return 1
|
# allow upgrade to version < 2.4.110 without checking ES version compatibility
|
||||||
}
|
return 0
|
||||||
|
else
|
||||||
# Gather Elasticsearch cluster version info and verify that each node in the cluster is running a version compatible with the target ES version.
|
target_es_version=$(sed -n '1p' <<< "$target_es_version_raw")
|
||||||
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
|
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
|
||||||
|
|
||||||
local es_required_version_statefile_value
|
local es_required_version_statefile_value=$(cat "$statefile")
|
||||||
es_required_version_statefile_value=$(cat "$statefile")
|
|
||||||
|
|
||||||
if [[ "$es_required_version_statefile_value" == "$target_es_version" ]]; then
|
if [[ "$es_required_version_statefile_value" == "$target_es_version" ]]; then
|
||||||
echo "Intermediate upgrade to ES $target_es_version is in progress. Skipping Elasticsearch version compatibility check."
|
echo "Intermediate upgrade to ES $target_es_version is in progress. Skipping Elasticsearch version compatibility check."
|
||||||
@@ -1235,14 +773,19 @@ verify_es_version_compatibility() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# use sort to check if es_required_statefile_value is < the current es_version.
|
# use sort to check if es_required_statefile_value is < the current es_version.
|
||||||
if [[ "$(printf '%s\n' "$es_required_version_statefile_value" "$es_version" | sort -V | head -n1)" == "$es_required_version_statefile_value" ]]; then
|
if [[ "$(printf '%s\n' $es_required_version_statefile_value $es_version | sort -V | head -n1)" == "$es_required_version_statefile_value" ]]; then
|
||||||
rm -f "$statefile"
|
rm -f "$statefile"
|
||||||
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
|
timeout --foreground 4000 bash "$es_verification_script" "$es_required_version_statefile_value" "$statefile"
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
echo -e "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
|
echo -e "\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
|
||||||
|
|
||||||
echo "A previous required intermediate Elasticsearch upgrade to $es_required_version_statefile_value has yet to successfully complete across the grid. Please allow time for all Searchnodes/Heavynodes to have upgraded Elasticsearch to $es_required_version_statefile_value before running soup again to avoid potential data loss!"
|
echo "A previous required intermediate Elasticsearch upgrade to $es_required_version_statefile_value has yet to successfully complete across the grid. Please allow time for all Searchnodes/Heavynodes to have upgraded Elasticsearch to $es_required_version_statefile_value before running soup again to avoid potential data loss!"
|
||||||
@@ -1259,28 +802,7 @@ verify_es_version_compatibility() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 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
|
||||||
@@ -1288,7 +810,7 @@ verify_es_version_compatibility() {
|
|||||||
if [[ -z "$compatible_versions" ]]; then
|
if [[ -z "$compatible_versions" ]]; then
|
||||||
# If current ES version is not explicitly defined in the upgrade map, we know they have an intermediate upgrade to do.
|
# If current ES version is not explicitly defined in the upgrade map, we know they have an intermediate upgrade to do.
|
||||||
# We default to the lowest ES version defined in es_to_so_version as $first_es_required_version
|
# We default to the lowest ES version defined in es_to_so_version as $first_es_required_version
|
||||||
first_es_required_version=$(printf '%s\n' "${!es_to_so_version[@]}" | sort -V | head -n1)
|
local first_es_required_version=$(printf '%s\n' "${!es_to_so_version[@]}" | sort -V | head -n1)
|
||||||
next_step_so_version=${es_to_so_version[$first_es_required_version]}
|
next_step_so_version=${es_to_so_version[$first_es_required_version]}
|
||||||
required_es_upgrade_version="$first_es_required_version"
|
required_es_upgrade_version="$first_es_required_version"
|
||||||
else
|
else
|
||||||
@@ -1307,7 +829,7 @@ verify_es_version_compatibility() {
|
|||||||
if [[ $is_airgap -eq 0 ]]; then
|
if [[ $is_airgap -eq 0 ]]; then
|
||||||
run_airgap_intermediate_upgrade
|
run_airgap_intermediate_upgrade
|
||||||
else
|
else
|
||||||
if [[ -n $ISOLOC ]]; then
|
if [[ ! -z $ISOLOC ]]; then
|
||||||
originally_requested_iso_location="$ISOLOC"
|
originally_requested_iso_location="$ISOLOC"
|
||||||
fi
|
fi
|
||||||
# Make sure ISOLOC is not set. Network installs that used soup -f would have ISOLOC set.
|
# Make sure ISOLOC is not set. Network installs that used soup -f would have ISOLOC set.
|
||||||
@@ -1339,8 +861,7 @@ wait_for_salt_minion_with_restart() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
run_airgap_intermediate_upgrade() {
|
run_airgap_intermediate_upgrade() {
|
||||||
local originally_requested_so_version
|
local originally_requested_so_version=$(cat $UPDATE_DIR/VERSION)
|
||||||
originally_requested_so_version=$(cat "$UPDATE_DIR/VERSION")
|
|
||||||
# preserve ISOLOC value, so we can try to use it post intermediate upgrade
|
# preserve ISOLOC value, so we can try to use it post intermediate upgrade
|
||||||
local originally_requested_iso_location="$ISOLOC"
|
local originally_requested_iso_location="$ISOLOC"
|
||||||
|
|
||||||
@@ -1352,8 +873,7 @@ run_airgap_intermediate_upgrade() {
|
|||||||
|
|
||||||
while [[ -z "$next_iso_location" ]] || [[ ! -f "$next_iso_location" && ! -b "$next_iso_location" ]]; do
|
while [[ -z "$next_iso_location" ]] || [[ ! -f "$next_iso_location" && ! -b "$next_iso_location" ]]; do
|
||||||
# List removable devices if any are present
|
# List removable devices if any are present
|
||||||
local removable_devices
|
local removable_devices=$(lsblk -no PATH,SIZE,TYPE,MOUNTPOINTS,RM | awk '$NF==1')
|
||||||
removable_devices=$(lsblk -no PATH,SIZE,TYPE,MOUNTPOINTS,RM | awk '$NF==1')
|
|
||||||
if [[ -n "$removable_devices" ]]; then
|
if [[ -n "$removable_devices" ]]; then
|
||||||
echo "PATH SIZE TYPE MOUNTPOINTS RM"
|
echo "PATH SIZE TYPE MOUNTPOINTS RM"
|
||||||
echo "$removable_devices"
|
echo "$removable_devices"
|
||||||
@@ -1374,21 +894,21 @@ run_airgap_intermediate_upgrade() {
|
|||||||
|
|
||||||
echo "Using $next_iso_location for required intermediary upgrade."
|
echo "Using $next_iso_location for required intermediary upgrade."
|
||||||
exec bash <<EOF
|
exec bash <<EOF
|
||||||
ISOLOC="$next_iso_location" soup -y && \
|
ISOLOC=$next_iso_location soup -y && \
|
||||||
ISOLOC="$next_iso_location" soup -y && \
|
ISOLOC=$next_iso_location soup -y && \
|
||||||
|
|
||||||
echo -e "\n##############################################################################################################################\n" && \
|
echo -e "\n##############################################################################################################################\n" && \
|
||||||
echo -e "Verifying Elasticsearch was successfully upgraded to $required_es_upgrade_version across the grid. This part can take a while as Searchnodes/Heavynodes sync up with the Manager! \n\nOnce verification completes the next soup will begin automatically. If verification takes longer than 1 hour it will stop waiting and your grid will remain at $next_step_so_version. Allowing for all Searchnodes/Heavynodes to upgrade Elasticsearch to the required version on their own time.\n" && \
|
echo -e "Verifying Elasticsearch was successfully upgraded to $required_es_upgrade_version across the grid. This part can take a while as Searchnodes/Heavynodes sync up with the Manager! \n\nOnce verification completes the next soup will begin automatically. If verification takes longer than 1 hour it will stop waiting and your grid will remain at $next_step_so_version. Allowing for all Searchnodes/Heavynodes to upgrade Elasticsearch to the required version on their own time.\n" && \
|
||||||
|
|
||||||
timeout --foreground 4000 bash /tmp/so_intermediate_upgrade_verification.sh "$required_es_upgrade_version" "$es_required_version_statefile" && \
|
timeout --foreground 4000 bash /tmp/so_intermediate_upgrade_verification.sh $required_es_upgrade_version $es_required_version_statefile && \
|
||||||
|
|
||||||
echo -e "\n##############################################################################################################################\n" && \
|
echo -e "\n##############################################################################################################################\n" && \
|
||||||
|
|
||||||
# automatically start the next soup if the original ISO isn't using the same block device we just used
|
# automatically start the next soup if the original ISO isn't using the same block device we just used
|
||||||
if [[ -n "$originally_requested_iso_location" ]] && [[ "$originally_requested_iso_location" != "$next_iso_location" ]]; then
|
if [[ -n "$originally_requested_iso_location" ]] && [[ "$originally_requested_iso_location" != "$next_iso_location" ]]; then
|
||||||
umount /tmp/soagupdate
|
umount /tmp/soagupdate
|
||||||
ISOLOC="$originally_requested_iso_location" soup -y && \
|
ISOLOC=$originally_requested_iso_location soup -y && \
|
||||||
ISOLOC="$originally_requested_iso_location" soup -y
|
ISOLOC=$originally_requested_iso_location soup -y
|
||||||
else
|
else
|
||||||
echo "Could not automatically start next soup to $originally_requested_so_version. Soup will now exit here at $(cat /etc/soversion)" && \
|
echo "Could not automatically start next soup to $originally_requested_so_version. Soup will now exit here at $(cat /etc/soversion)" && \
|
||||||
|
|
||||||
@@ -1404,29 +924,29 @@ run_network_intermediate_upgrade() {
|
|||||||
if [[ -n "$BRANCH" ]]; then
|
if [[ -n "$BRANCH" ]]; then
|
||||||
local originally_requested_so_branch="$BRANCH"
|
local originally_requested_so_branch="$BRANCH"
|
||||||
else
|
else
|
||||||
local originally_requested_so_branch="3/main"
|
local originally_requested_so_branch="2.4/main"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Starting automated intermediate upgrade to $next_step_so_version."
|
echo "Starting automated intermediate upgrade to $next_step_so_version."
|
||||||
echo "After completion, the system will automatically attempt to upgrade to the latest version."
|
echo "After completion, the system will automatically attempt to upgrade to the latest version."
|
||||||
echo -e "\n##############################################################################################################################\n"
|
echo -e "\n##############################################################################################################################\n"
|
||||||
exec bash << EOF
|
exec bash << EOF
|
||||||
BRANCH="$next_step_so_version" soup -y && \
|
BRANCH=$next_step_so_version soup -y && \
|
||||||
BRANCH="$next_step_so_version" soup -y && \
|
BRANCH=$next_step_so_version soup -y && \
|
||||||
|
|
||||||
echo -e "\n##############################################################################################################################\n" && \
|
echo -e "\n##############################################################################################################################\n" && \
|
||||||
echo -e "Verifying Elasticsearch was successfully upgraded to $required_es_upgrade_version across the grid. This part can take a while as Searchnodes/Heavynodes sync up with the Manager! \n\nOnce verification completes the next soup will begin automatically. If verification takes longer than 1 hour it will stop waiting and your grid will remain at $next_step_so_version. Allowing for all Searchnodes/Heavynodes to upgrade Elasticsearch to the required version on their own time.\n" && \
|
echo -e "Verifying Elasticsearch was successfully upgraded to $required_es_upgrade_version across the grid. This part can take a while as Searchnodes/Heavynodes sync up with the Manager! \n\nOnce verification completes the next soup will begin automatically. If verification takes longer than 1 hour it will stop waiting and your grid will remain at $next_step_so_version. Allowing for all Searchnodes/Heavynodes to upgrade Elasticsearch to the required version on their own time.\n" && \
|
||||||
|
|
||||||
timeout --foreground 4000 bash /tmp/so_intermediate_upgrade_verification.sh "$required_es_upgrade_version" "$es_required_version_statefile" && \
|
timeout --foreground 4000 bash /tmp/so_intermediate_upgrade_verification.sh $required_es_upgrade_version $es_required_version_statefile && \
|
||||||
|
|
||||||
echo -e "\n##############################################################################################################################\n" && \
|
echo -e "\n##############################################################################################################################\n" && \
|
||||||
if [[ -n "$originally_requested_iso_location" ]]; then
|
if [[ -n "$originally_requested_iso_location" ]]; then
|
||||||
# nonairgap soup that used -f originally, runs intermediate upgrade using network + BRANCH, later coming back to the original ISO for the last soup
|
# nonairgap soup that used -f originally, runs intermediate upgrade using network + BRANCH, later coming back to the original ISO for the last soup
|
||||||
ISOLOC="$originally_requested_iso_location" soup -y && \
|
ISOLOC=$originally_requested_iso_location soup -y && \
|
||||||
ISOLOC="$originally_requested_iso_location" soup -y
|
ISOLOC=$originally_requested_iso_location soup -y
|
||||||
else
|
else
|
||||||
BRANCH="$originally_requested_so_branch" soup -y && \
|
BRANCH=$originally_requested_so_branch soup -y && \
|
||||||
BRANCH="$originally_requested_so_branch" soup -y
|
BRANCH=$originally_requested_so_branch soup -y
|
||||||
fi
|
fi
|
||||||
echo -e "\n##############################################################################################################################\n"
|
echo -e "\n##############################################################################################################################\n"
|
||||||
EOF
|
EOF
|
||||||
@@ -1566,7 +1086,7 @@ EOF
|
|||||||
|
|
||||||
# Keeping this block in case we need to do a hotfix that requires salt update
|
# Keeping this block in case we need to do a hotfix that requires salt update
|
||||||
apply_hotfix() {
|
apply_hotfix() {
|
||||||
echo "No actions required. ($INSTALLEDVERSION/$HOTFIXVERSION)"
|
echo "No actions required. ($INSTALLEDVERSION/$HOTFIXVERSION)"
|
||||||
}
|
}
|
||||||
|
|
||||||
failed_soup_restore_items() {
|
failed_soup_restore_items() {
|
||||||
@@ -1638,13 +1158,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
|
||||||
@@ -1664,8 +1184,7 @@ 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
|
||||||
echo "Running so-config-backup script."
|
backup_old_states_pillars
|
||||||
/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"
|
||||||
@@ -1721,8 +1240,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 "Running so-config-backup script."
|
echo "Creating snapshots of default and local Salt states and pillars and saving to /nsm/backup/"
|
||||||
/sbin/so-config-backup
|
backup_old_states_pillars
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -225,7 +225,6 @@ http {
|
|||||||
limit_req zone=auth_throttle burst={{ NGINXMERGED.config.throttle_login_burst }} nodelay;
|
limit_req zone=auth_throttle burst={{ NGINXMERGED.config.throttle_login_burst }} nodelay;
|
||||||
limit_req_status 429;
|
limit_req_status 429;
|
||||||
proxy_pass http://{{ GLOBALS.manager }}:4433;
|
proxy_pass http://{{ GLOBALS.manager }}:4433;
|
||||||
proxy_set_header Connection "Close";
|
|
||||||
proxy_read_timeout 90;
|
proxy_read_timeout 90;
|
||||||
proxy_connect_timeout 90;
|
proxy_connect_timeout 90;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
@@ -238,7 +237,6 @@ http {
|
|||||||
location ~ ^/auth/.*?(whoami|logout|settings|errors|webauthn.js) {
|
location ~ ^/auth/.*?(whoami|logout|settings|errors|webauthn.js) {
|
||||||
rewrite /auth/(.*) /$1 break;
|
rewrite /auth/(.*) /$1 break;
|
||||||
proxy_pass http://{{ GLOBALS.manager }}:4433;
|
proxy_pass http://{{ GLOBALS.manager }}:4433;
|
||||||
proxy_set_header Connection "Close";
|
|
||||||
proxy_read_timeout 90;
|
proxy_read_timeout 90;
|
||||||
proxy_connect_timeout 90;
|
proxy_connect_timeout 90;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
|
|||||||
@@ -3,14 +3,7 @@
|
|||||||
# https://securityonion.net/license; you may not use this file except in compliance with the
|
# https://securityonion.net/license; you may not use this file except in compliance with the
|
||||||
# Elastic License 2.0.
|
# Elastic License 2.0.
|
||||||
|
|
||||||
{% set hypervisor = pillar.get('minion_id', '') %}
|
{% set hypervisor = pillar.minion_id %}
|
||||||
|
|
||||||
{% if not hypervisor|regex_match('^([A-Za-z0-9._-]{1,253})$') %}
|
|
||||||
{% do salt.log.error('delete_hypervisor_orch: refusing unsafe minion_id=' ~ hypervisor) %}
|
|
||||||
delete_hypervisor_invalid_minion_id:
|
|
||||||
test.fail_without_changes:
|
|
||||||
- name: delete_hypervisor_invalid_minion_id
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
ensure_hypervisor_mine_deleted:
|
ensure_hypervisor_mine_deleted:
|
||||||
salt.function:
|
salt.function:
|
||||||
@@ -27,5 +20,3 @@ update_salt_cloud_profile:
|
|||||||
- sls:
|
- sls:
|
||||||
- salt.cloud.config
|
- salt.cloud.config
|
||||||
- concurrent: True
|
- concurrent: True
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|||||||
@@ -25,33 +25,8 @@ manager_run_es_soc:
|
|||||||
- salt: {{NEWNODE}}_update_mine
|
- salt: {{NEWNODE}}_update_mine
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
# so-minion has already added the new minion's entry to telegraf/creds.sls
|
|
||||||
# via so-telegraf-cred before this orch fires. Reconcile the Postgres role
|
|
||||||
# on the manager so the new minion can authenticate on its first highstate,
|
|
||||||
# then refresh the minion's pillar so its telegraf.conf renders with the
|
|
||||||
# freshly-written cred.
|
|
||||||
manager_create_postgres_telegraf_role:
|
|
||||||
salt.state:
|
|
||||||
- tgt: {{ MANAGER }}
|
|
||||||
- sls:
|
|
||||||
- postgres.telegraf_users
|
|
||||||
- queue: True
|
|
||||||
- require:
|
|
||||||
- salt: {{NEWNODE}}_update_mine
|
|
||||||
|
|
||||||
{{NEWNODE}}_refresh_pillar:
|
|
||||||
salt.function:
|
|
||||||
- name: saltutil.refresh_pillar
|
|
||||||
- tgt: {{ NEWNODE }}
|
|
||||||
- kwarg:
|
|
||||||
wait: True
|
|
||||||
- require:
|
|
||||||
- salt: manager_create_postgres_telegraf_role
|
|
||||||
|
|
||||||
{{NEWNODE}}_run_highstate:
|
{{NEWNODE}}_run_highstate:
|
||||||
salt.state:
|
salt.state:
|
||||||
- tgt: {{ NEWNODE }}
|
- tgt: {{ NEWNODE }}
|
||||||
- highstate: True
|
- highstate: True
|
||||||
- queue: True
|
- queue: True
|
||||||
- require:
|
|
||||||
- salt: {{NEWNODE}}_refresh_pillar
|
|
||||||
|
|||||||
@@ -12,14 +12,7 @@
|
|||||||
{% if 'vrt' in salt['pillar.get']('features', []) %}
|
{% if 'vrt' in salt['pillar.get']('features', []) %}
|
||||||
|
|
||||||
{% do salt.log.debug('vm_pillar_clean_orch: Running') %}
|
{% do salt.log.debug('vm_pillar_clean_orch: Running') %}
|
||||||
{% set vm_name = pillar.get('vm_name', '') %}
|
{% set vm_name = pillar.get('vm_name') %}
|
||||||
|
|
||||||
{% if not vm_name|regex_match('^([A-Za-z0-9._-]{1,253})$') %}
|
|
||||||
{% do salt.log.error('vm_pillar_clean_orch: refusing unsafe vm_name=' ~ vm_name) %}
|
|
||||||
vm_pillar_clean_invalid_name:
|
|
||||||
test.fail_without_changes:
|
|
||||||
- name: vm_pillar_clean_invalid_name
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
delete_adv_{{ vm_name }}_pillar:
|
delete_adv_{{ vm_name }}_pillar:
|
||||||
module.run:
|
module.run:
|
||||||
@@ -31,8 +24,6 @@ delete_{{ vm_name }}_pillar:
|
|||||||
- file.remove:
|
- file.remove:
|
||||||
- path: /opt/so/saltstack/local/pillar/minions/{{ vm_name }}.sls
|
- path: /opt/so/saltstack/local/pillar/minions/{{ vm_name }}.sls
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
{% do salt.log.error(
|
{% do salt.log.error(
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
|
|
||||||
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
|
|
||||||
# https://securityonion.net/license; you may not use this file except in compliance with the
|
|
||||||
# Elastic License 2.0.
|
|
||||||
|
|
||||||
{% from 'allowed_states.map.jinja' import allowed_states %}
|
|
||||||
{% if sls in allowed_states %}
|
|
||||||
|
|
||||||
{% set DIGITS = "1234567890" %}
|
|
||||||
{% set LOWERCASE = "qwertyuiopasdfghjklzxcvbnm" %}
|
|
||||||
{% set UPPERCASE = "QWERTYUIOPASDFGHJKLZXCVBNM" %}
|
|
||||||
{% set SYMBOLS = "~!@#^&*()-_=+[]|;:,.<>?" %}
|
|
||||||
{% set CHARS = DIGITS~LOWERCASE~UPPERCASE~SYMBOLS %}
|
|
||||||
{% set so_postgres_user_pass = salt['pillar.get']('postgres:auth:users:so_postgres_user:pass', salt['random.get_str'](72, chars=CHARS)) %}
|
|
||||||
|
|
||||||
# Admin cred only. Per-minion Telegraf creds live in telegraf/creds.sls,
|
|
||||||
# managed by /usr/sbin/so-telegraf-cred (called from so-minion).
|
|
||||||
postgres_auth_pillar:
|
|
||||||
file.managed:
|
|
||||||
- name: /opt/so/saltstack/local/pillar/postgres/auth.sls
|
|
||||||
- mode: 640
|
|
||||||
- reload_pillar: True
|
|
||||||
- contents: |
|
|
||||||
postgres:
|
|
||||||
auth:
|
|
||||||
users:
|
|
||||||
so_postgres_user:
|
|
||||||
user: so_postgres
|
|
||||||
pass: "{{ so_postgres_user_pass }}"
|
|
||||||
- show_changes: False
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
{{sls}}_state_not_allowed:
|
|
||||||
test.fail_without_changes:
|
|
||||||
- name: {{sls}}_state_not_allowed
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
|
|
||||||
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
|
|
||||||
# https://securityonion.net/license; you may not use this file except in compliance with the
|
|
||||||
# Elastic License 2.0.
|
|
||||||
|
|
||||||
{% from 'allowed_states.map.jinja' import allowed_states %}
|
|
||||||
{% if sls.split('.')[0] in allowed_states %}
|
|
||||||
{% from 'postgres/map.jinja' import PGMERGED %}
|
|
||||||
|
|
||||||
# Postgres Setup
|
|
||||||
postgresconfdir:
|
|
||||||
file.directory:
|
|
||||||
- name: /opt/so/conf/postgres
|
|
||||||
- user: 939
|
|
||||||
- group: 939
|
|
||||||
- makedirs: True
|
|
||||||
|
|
||||||
postgressecretsdir:
|
|
||||||
file.directory:
|
|
||||||
- name: /opt/so/conf/postgres/secrets
|
|
||||||
- user: 939
|
|
||||||
- group: 939
|
|
||||||
- mode: 700
|
|
||||||
- require:
|
|
||||||
- file: postgresconfdir
|
|
||||||
|
|
||||||
postgresdatadir:
|
|
||||||
file.directory:
|
|
||||||
- name: /nsm/postgres
|
|
||||||
- user: 939
|
|
||||||
- group: 939
|
|
||||||
- makedirs: True
|
|
||||||
|
|
||||||
postgreslogdir:
|
|
||||||
file.directory:
|
|
||||||
- name: /opt/so/log/postgres
|
|
||||||
- user: 939
|
|
||||||
- group: 939
|
|
||||||
- makedirs: True
|
|
||||||
|
|
||||||
postgresinitdir:
|
|
||||||
file.directory:
|
|
||||||
- name: /opt/so/conf/postgres/init
|
|
||||||
- user: 939
|
|
||||||
- group: 939
|
|
||||||
- require:
|
|
||||||
- file: postgresconfdir
|
|
||||||
|
|
||||||
postgresinitdb:
|
|
||||||
file.managed:
|
|
||||||
- name: /opt/so/conf/postgres/init/init-db.sh
|
|
||||||
- source: salt://postgres/files/init-db.sh
|
|
||||||
- user: 939
|
|
||||||
- group: 939
|
|
||||||
- mode: 755
|
|
||||||
|
|
||||||
postgresconf:
|
|
||||||
file.managed:
|
|
||||||
- name: /opt/so/conf/postgres/postgresql.conf
|
|
||||||
- source: salt://postgres/files/postgresql.conf.jinja
|
|
||||||
- user: 939
|
|
||||||
- group: 939
|
|
||||||
- template: jinja
|
|
||||||
- defaults:
|
|
||||||
PGMERGED: {{ PGMERGED }}
|
|
||||||
|
|
||||||
postgreshba:
|
|
||||||
file.managed:
|
|
||||||
- name: /opt/so/conf/postgres/pg_hba.conf
|
|
||||||
- source: salt://postgres/files/pg_hba.conf
|
|
||||||
- user: 939
|
|
||||||
- group: 939
|
|
||||||
- mode: 640
|
|
||||||
|
|
||||||
postgres_super_secret:
|
|
||||||
file.managed:
|
|
||||||
- name: /opt/so/conf/postgres/secrets/postgres_password
|
|
||||||
- user: 939
|
|
||||||
- group: 939
|
|
||||||
- mode: 600
|
|
||||||
- contents_pillar: 'secrets:postgres_pass'
|
|
||||||
- show_changes: False
|
|
||||||
- require:
|
|
||||||
- file: postgressecretsdir
|
|
||||||
|
|
||||||
postgres_app_secret:
|
|
||||||
file.managed:
|
|
||||||
- name: /opt/so/conf/postgres/secrets/so_postgres_pass
|
|
||||||
- user: 939
|
|
||||||
- group: 939
|
|
||||||
- mode: 600
|
|
||||||
- contents_pillar: 'postgres:auth:users:so_postgres_user:pass'
|
|
||||||
- show_changes: False
|
|
||||||
- require:
|
|
||||||
- file: postgressecretsdir
|
|
||||||
|
|
||||||
postgres_sbin:
|
|
||||||
file.recurse:
|
|
||||||
- name: /usr/sbin
|
|
||||||
- source: salt://postgres/tools/sbin
|
|
||||||
- user: root
|
|
||||||
- group: root
|
|
||||||
- file_mode: 755
|
|
||||||
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
{{sls}}_state_not_allowed:
|
|
||||||
test.fail_without_changes:
|
|
||||||
- name: {{sls}}_state_not_allowed
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
postgres:
|
|
||||||
enabled: True
|
|
||||||
telegraf:
|
|
||||||
retention_days: 14
|
|
||||||
config:
|
|
||||||
listen_addresses: '*'
|
|
||||||
port: 5432
|
|
||||||
max_connections: 100
|
|
||||||
shared_buffers: 256MB
|
|
||||||
ssl: 'on'
|
|
||||||
ssl_cert_file: '/conf/postgres.crt'
|
|
||||||
ssl_key_file: '/conf/postgres.key'
|
|
||||||
ssl_ca_file: '/conf/ca.crt'
|
|
||||||
hba_file: '/conf/pg_hba.conf'
|
|
||||||
log_destination: 'stderr'
|
|
||||||
logging_collector: 'off'
|
|
||||||
log_min_messages: 'warning'
|
|
||||||
shared_preload_libraries: pg_cron
|
|
||||||
cron.database_name: so_telegraf
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user