Compare commits

..

2 Commits

Author SHA1 Message Date
DefensiveDepth
422b4bc4c9 Add local custom Playbooks 2025-09-18 12:22:20 -04:00
DefensiveDepth
6cdd88808a Add local custom Playbooks 2025-09-18 12:07:21 -04:00
120 changed files with 769 additions and 5173 deletions

View File

@@ -32,7 +32,6 @@ body:
- 2.4.170
- 2.4.180
- 2.4.190
- 2.4.200
- Other (please provide detail below)
validations:
required: true

View File

@@ -4,7 +4,7 @@ on:
pull_request:
paths:
- "salt/sensoroni/files/analyzers/**"
- "salt/manager/tools/sbin/**"
- "salt/manager/tools/sbin"
jobs:
build:

View File

@@ -1,17 +1,17 @@
### 2.4.190-20251024 ISO image released on 2025/10/24
### 2.4.180-20250916 ISO image released on 2025/09/17
### Download and Verify
2.4.190-20251024 ISO image:
https://download.securityonion.net/file/securityonion/securityonion-2.4.190-20251024.iso
2.4.180-20250916 ISO image:
https://download.securityonion.net/file/securityonion/securityonion-2.4.180-20250916.iso
MD5: 25358481FB876226499C011FC0710358
SHA1: 0B26173C0CE136F2CA40A15046D1DFB78BCA1165
SHA256: 4FD9F62EDA672408828B3C0C446FE5EA9FF3C4EE8488A7AB1101544A3C487872
MD5: DE93880E38DE4BE45D05A41E1745CB1F
SHA1: AEA6948911E50A4A38E8729E0E965C565402E3FC
SHA256: C9BD8CA071E43B048ABF9ED145B87935CB1D4BB839B2244A06FAD1BBA8EAC84A
Signature for ISO image:
https://github.com/Security-Onion-Solutions/securityonion/raw/2.4/main/sigs/securityonion-2.4.190-20251024.iso.sig
https://github.com/Security-Onion-Solutions/securityonion/raw/2.4/main/sigs/securityonion-2.4.180-20250916.iso.sig
Signing key:
https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/2.4/main/KEYS
@@ -25,22 +25,22 @@ wget https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/2.
Download the signature file for the ISO:
```
wget https://github.com/Security-Onion-Solutions/securityonion/raw/2.4/main/sigs/securityonion-2.4.190-20251024.iso.sig
wget https://github.com/Security-Onion-Solutions/securityonion/raw/2.4/main/sigs/securityonion-2.4.180-20250916.iso.sig
```
Download the ISO image:
```
wget https://download.securityonion.net/file/securityonion/securityonion-2.4.190-20251024.iso
wget https://download.securityonion.net/file/securityonion/securityonion-2.4.180-20250916.iso
```
Verify the downloaded ISO image using the signature file:
```
gpg --verify securityonion-2.4.190-20251024.iso.sig securityonion-2.4.190-20251024.iso
gpg --verify securityonion-2.4.180-20250916.iso.sig securityonion-2.4.180-20250916.iso
```
The output should show "Good signature" and the Primary key fingerprint should match what's shown below:
```
gpg: Signature made Thu 23 Oct 2025 07:21:46 AM EDT using RSA key ID FE507013
gpg: Signature made Tue 16 Sep 2025 06:30:19 PM EDT using RSA key ID FE507013
gpg: Good signature from "Security Onion Solutions, LLC <info@securityonionsolutions.com>"
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.

View File

@@ -1 +1 @@
2.4.200
2.4.190

View File

@@ -1,91 +0,0 @@
#!/opt/saltstack/salt/bin/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.
#
# Note: Per the Elastic License 2.0, the second limitation states:
#
# "You may not move, change, disable, or circumvent the license key functionality
# in the software, and you may not remove or obscure any functionality in the
# software that is protected by the license key."
"""
Salt execution module for hypervisor operations.
This module provides functions for managing hypervisor configurations,
including VM file management.
"""
import json
import logging
import os
log = logging.getLogger(__name__)
__virtualname__ = 'hypervisor'
def __virtual__():
"""
Only load this module if we're on a system that can manage hypervisors.
"""
return __virtualname__
def remove_vm_from_vms_file(vms_file_path, vm_hostname, vm_role):
"""
Remove a VM entry from the hypervisorVMs file.
Args:
vms_file_path (str): Path to the hypervisorVMs file
vm_hostname (str): Hostname of the VM to remove (without role suffix)
vm_role (str): Role of the VM
Returns:
dict: Result dictionary with success status and message
CLI Example:
salt '*' hypervisor.remove_vm_from_vms_file /opt/so/saltstack/local/salt/hypervisor/hosts/hypervisor1VMs node1 nsm
"""
try:
# Check if file exists
if not os.path.exists(vms_file_path):
msg = f"VMs file not found: {vms_file_path}"
log.error(msg)
return {'result': False, 'comment': msg}
# Read current VMs
with open(vms_file_path, 'r') as f:
content = f.read().strip()
vms = json.loads(content) if content else []
# Find and remove the VM entry
original_count = len(vms)
vms = [vm for vm in vms if not (vm.get('hostname') == vm_hostname and vm.get('role') == vm_role)]
if len(vms) < original_count:
# VM was found and removed, write back to file
with open(vms_file_path, 'w') as f:
json.dump(vms, f, indent=2)
# Set socore:socore ownership (939:939)
os.chown(vms_file_path, 939, 939)
msg = f"Removed VM {vm_hostname}_{vm_role} from {vms_file_path}"
log.info(msg)
return {'result': True, 'comment': msg}
else:
msg = f"VM {vm_hostname}_{vm_role} not found in {vms_file_path}"
log.warning(msg)
return {'result': False, 'comment': msg}
except json.JSONDecodeError as e:
msg = f"Failed to parse JSON in {vms_file_path}: {str(e)}"
log.error(msg)
return {'result': False, 'comment': msg}
except Exception as e:
msg = f"Failed to remove VM {vm_hostname}_{vm_role} from {vms_file_path}: {str(e)}"
log.error(msg)
return {'result': False, 'comment': msg}

View File

@@ -7,14 +7,12 @@
"""
Salt module for managing QCOW2 image configurations and VM hardware settings. This module provides functions
for modifying network configurations within QCOW2 images, adjusting virtual machine hardware settings, and
creating virtual storage volumes. It serves as a Salt interface to the so-qcow2-modify-network,
so-kvm-modify-hardware, and so-kvm-create-volume scripts.
for modifying network configurations within QCOW2 images and adjusting virtual machine hardware settings.
It serves as a Salt interface to the so-qcow2-modify-network and so-kvm-modify-hardware scripts.
The module offers three main capabilities:
The module offers two main capabilities:
1. Network Configuration: Modify network settings (DHCP/static IP) within QCOW2 images
2. Hardware Configuration: Adjust VM hardware settings (CPU, memory, PCI passthrough)
3. Volume Management: Create and attach virtual storage volumes for NSM data
This module is intended to work with Security Onion's virtualization infrastructure and is typically
used in conjunction with salt-cloud for VM provisioning and management.
@@ -246,90 +244,3 @@ def modify_hardware_config(vm_name, cpu=None, memory=None, pci=None, start=False
except Exception as e:
log.error('qcow2 module: An error occurred while executing the script: {}'.format(e))
raise
def create_volume_config(vm_name, size_gb, start=False):
'''
Usage:
salt '*' qcow2.create_volume_config vm_name=<name> size_gb=<size> [start=<bool>]
Options:
vm_name
Name of the virtual machine to attach the volume to
size_gb
Volume size in GB (positive integer)
This determines the capacity of the virtual storage volume
start
Boolean flag to start the VM after volume creation
Optional - defaults to False
Examples:
1. **Create 500GB Volume:**
```bash
salt '*' qcow2.create_volume_config vm_name='sensor1_sensor' size_gb=500
```
This creates a 500GB virtual volume for NSM storage
2. **Create 1TB Volume and Start VM:**
```bash
salt '*' qcow2.create_volume_config vm_name='sensor1_sensor' size_gb=1000 start=True
```
This creates a 1TB volume and starts the VM after attachment
Notes:
- VM must be stopped before volume creation
- Volume is created as a qcow2 image and attached to the VM
- This is an alternative to disk passthrough via modify_hardware_config
- Volume is automatically attached to the VM's libvirt configuration
- Requires so-kvm-create-volume script to be installed
- Volume files are stored in the hypervisor's VM storage directory
Description:
This function creates and attaches a virtual storage volume to a KVM virtual machine
using the so-kvm-create-volume script. It creates a qcow2 disk image of the specified
size and attaches it to the VM for NSM (Network Security Monitoring) storage purposes.
This provides an alternative to physical disk passthrough, allowing flexible storage
allocation without requiring dedicated hardware. The VM can optionally be started
after the volume is successfully created and attached.
Exit Codes:
0: Success
1: Invalid parameters
2: VM state error (running when should be stopped)
3: Volume creation error
4: System command error
255: Unexpected error
Logging:
- All operations are logged to the salt minion log
- Log entries are prefixed with 'qcow2 module:'
- Volume creation and attachment operations are logged
- Errors include detailed messages and stack traces
- Final status of volume creation is logged
'''
# Validate size_gb parameter
if not isinstance(size_gb, int) or size_gb <= 0:
raise ValueError('size_gb must be a positive integer.')
cmd = ['/usr/sbin/so-kvm-create-volume', '-v', vm_name, '-s', str(size_gb)]
if start:
cmd.append('-S')
log.info('qcow2 module: Executing command: {}'.format(' '.join(shlex.quote(arg) for arg in cmd)))
try:
result = subprocess.run(cmd, capture_output=True, text=True, check=False)
ret = {
'retcode': result.returncode,
'stdout': result.stdout,
'stderr': result.stderr
}
if result.returncode != 0:
log.error('qcow2 module: Script execution failed with return code {}: {}'.format(result.returncode, result.stderr))
else:
log.info('qcow2 module: Script executed successfully.')
return ret
except Exception as e:
log.error('qcow2 module: An error occurred while executing the script: {}'.format(e))
raise

View File

@@ -172,15 +172,7 @@ MANAGER_HOSTNAME = socket.gethostname()
def _download_image():
"""
Download and validate the Oracle Linux KVM image with retry logic and progress monitoring.
Features:
- Detects stalled downloads (no progress for 30 seconds)
- Retries up to 3 times on failure
- Connection timeout of 30 seconds
- Read timeout of 60 seconds
- Cleans up partial downloads on failure
Download and validate the Oracle Linux KVM image.
Returns:
bool: True if successful or file exists with valid checksum, False on error
"""
@@ -194,54 +186,25 @@ def _download_image():
log.info("Starting image download process")
# Retry configuration
max_attempts = 3
retry_delay = 5 # seconds to wait between retry attempts
stall_timeout = 30 # seconds without progress before considering download stalled
connection_timeout = 30 # seconds to establish connection
read_timeout = 60 # seconds to wait for data chunks
for attempt in range(1, max_attempts + 1):
log.info("Download attempt %d of %d", attempt, max_attempts)
try:
# Download file with timeouts
# Download file
log.info("Downloading Oracle Linux KVM image from %s to %s", IMAGE_URL, IMAGE_PATH)
response = requests.get(
IMAGE_URL,
stream=True,
timeout=(connection_timeout, read_timeout)
)
response = requests.get(IMAGE_URL, stream=True)
response.raise_for_status()
# Get total file size for progress tracking
total_size = int(response.headers.get('content-length', 0))
downloaded_size = 0
last_log_time = 0
last_progress_time = time.time()
last_downloaded_size = 0
# Save file with progress logging and stall detection
# Save file with progress logging
with salt.utils.files.fopen(IMAGE_PATH, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
if chunk: # filter out keep-alive new chunks
f.write(chunk)
downloaded_size += len(chunk)
current_time = time.time()
# Check for stalled download
if downloaded_size > last_downloaded_size:
# Progress made, reset stall timer
last_progress_time = current_time
last_downloaded_size = downloaded_size
elif current_time - last_progress_time > stall_timeout:
# No progress for stall_timeout seconds
raise Exception(
f"Download stalled: no progress for {stall_timeout} seconds "
f"at {downloaded_size}/{total_size} bytes"
)
# Log progress every second
current_time = time.time()
if current_time - last_log_time >= 1:
progress = (downloaded_size / total_size) * 100 if total_size > 0 else 0
log.info("Progress - %.1f%% (%d/%d bytes)",
@@ -249,50 +212,17 @@ def _download_image():
last_log_time = current_time
# Validate downloaded file
log.info("Download complete, validating checksum...")
if not _validate_image_checksum(IMAGE_PATH, IMAGE_SHA256):
log.error("Checksum validation failed on attempt %d", attempt)
os.unlink(IMAGE_PATH)
if attempt < max_attempts:
log.info("Will retry download...")
continue
else:
log.error("All download attempts failed due to checksum mismatch")
return False
log.info("Successfully downloaded and validated Oracle Linux KVM image")
return True
except requests.exceptions.Timeout as e:
log.error("Download attempt %d failed: Timeout - %s", attempt, str(e))
if os.path.exists(IMAGE_PATH):
os.unlink(IMAGE_PATH)
if attempt < max_attempts:
log.info("Will retry download in %d seconds...", retry_delay)
time.sleep(retry_delay)
else:
log.error("All download attempts failed due to timeout")
except requests.exceptions.RequestException as e:
log.error("Download attempt %d failed: Network error - %s", attempt, str(e))
if os.path.exists(IMAGE_PATH):
os.unlink(IMAGE_PATH)
if attempt < max_attempts:
log.info("Will retry download in %d seconds...", retry_delay)
time.sleep(retry_delay)
else:
log.error("All download attempts failed due to network errors")
except Exception as e:
log.error("Download attempt %d failed: %s", attempt, str(e))
log.error("Error downloading hypervisor image: %s", str(e))
if os.path.exists(IMAGE_PATH):
os.unlink(IMAGE_PATH)
if attempt < max_attempts:
log.info("Will retry download in %d seconds...", retry_delay)
time.sleep(retry_delay)
else:
log.error("All download attempts failed")
return False
def _check_ssh_keys_exist():
@@ -489,28 +419,25 @@ def _ensure_hypervisor_host_dir(minion_id: str = None):
log.error(f"Error creating hypervisor host directory: {str(e)}")
return False
def _apply_dyanno_hypervisor_state(status):
def _apply_dyanno_hypervisor_state():
"""
Apply the soc.dyanno.hypervisor state on the salt master.
This function applies the soc.dyanno.hypervisor state on the salt master
to update the hypervisor annotation and ensure all hypervisor host directories exist.
Args:
status: Status passed to the hypervisor annotation state
Returns:
bool: True if state was applied successfully, False otherwise
"""
try:
log.info(f"Applying soc.dyanno.hypervisor state on salt master with status: {status}")
log.info("Applying soc.dyanno.hypervisor state on salt master")
# Initialize the LocalClient
local = salt.client.LocalClient()
# Target the salt master to apply the soc.dyanno.hypervisor state
target = MANAGER_HOSTNAME + '_*'
state_result = local.cmd(target, 'state.apply', ['soc.dyanno.hypervisor', f"pillar={{'baseDomain': {{'status': '{status}'}}}}", 'concurrent=True'], tgt_type='glob')
state_result = local.cmd(target, 'state.apply', ['soc.dyanno.hypervisor', "pillar={'baseDomain': {'status': 'PreInit'}}", 'concurrent=True'], tgt_type='glob')
log.debug(f"state_result: {state_result}")
# Check if state was applied successfully
if state_result:
@@ -527,17 +454,17 @@ def _apply_dyanno_hypervisor_state(status):
success = False
if success:
log.info(f"Successfully applied soc.dyanno.hypervisor state with status: {status}")
log.info("Successfully applied soc.dyanno.hypervisor state")
return True
else:
log.error(f"Failed to apply soc.dyanno.hypervisor state with status: {status}")
log.error("Failed to apply soc.dyanno.hypervisor state")
return False
else:
log.error(f"No response from salt master when applying soc.dyanno.hypervisor state with status: {status}")
log.error("No response from salt master when applying soc.dyanno.hypervisor state")
return False
except Exception as e:
log.error(f"Error applying soc.dyanno.hypervisor state with status: {status}: {str(e)}")
log.error(f"Error applying soc.dyanno.hypervisor state: {str(e)}")
return False
def _apply_cloud_config_state():
@@ -671,6 +598,11 @@ def setup_environment(vm_name: str = 'sool9', disk_size: str = '220G', minion_id
log.warning("Failed to apply salt.cloud.config state, continuing with setup")
# We don't return an error here as we want to continue with the setup process
# Apply the soc.dyanno.hypervisor state on the salt master
if not _apply_dyanno_hypervisor_state():
log.warning("Failed to apply soc.dyanno.hypervisor state, continuing with setup")
# We don't return an error here as we want to continue with the setup process
log.info("Starting setup_environment in setup_hypervisor runner")
# Check if environment is already set up
@@ -684,12 +616,9 @@ def setup_environment(vm_name: str = 'sool9', disk_size: str = '220G', minion_id
# Handle image setup if needed
if not image_valid:
_apply_dyanno_hypervisor_state('ImageDownloadStart')
log.info("Starting image download/validation process")
if not _download_image():
log.error("Image download failed")
# Update hypervisor annotation with failure status
_apply_dyanno_hypervisor_state('ImageDownloadFailed')
return {
'success': False,
'error': 'Image download failed',
@@ -702,8 +631,6 @@ def setup_environment(vm_name: str = 'sool9', disk_size: str = '220G', minion_id
log.info("Setting up SSH keys")
if not _setup_ssh_keys():
log.error("SSH key setup failed")
# Update hypervisor annotation with failure status
_apply_dyanno_hypervisor_state('SSHKeySetupFailed')
return {
'success': False,
'error': 'SSH key setup failed',
@@ -728,12 +655,6 @@ def setup_environment(vm_name: str = 'sool9', disk_size: str = '220G', minion_id
success = vm_result.get('success', False)
log.info("Setup environment completed with status: %s", "SUCCESS" if success else "FAILED")
# Update hypervisor annotation with success status
if success:
_apply_dyanno_hypervisor_state('PreInit')
else:
_apply_dyanno_hypervisor_state('SetupFailed')
# If setup was successful and we have a minion_id, run highstate
if success and minion_id:
log.info("Running highstate on hypervisor %s", minion_id)

View File

@@ -1,7 +1,4 @@
{% from 'vars/globals.map.jinja' import GLOBALS %}
{% set PCAP_BPF_STATUS = 0 %}
{% set STENO_BPF_COMPILED = "" %}
{% if GLOBALS.pcap_engine == "TRANSITION" %}
{% set PCAPBPF = ["ip and host 255.255.255.1 and port 1"] %}
{% else %}
@@ -11,11 +8,3 @@
{{ MACROS.remove_comments(BPFMERGED, 'pcap') }}
{% set PCAPBPF = BPFMERGED.pcap %}
{% endif %}
{% if PCAPBPF %}
{% set PCAP_BPF_CALC = salt['cmd.run_all']('/usr/sbin/so-bpf-compile ' ~ GLOBALS.sensor.interface ~ ' ' ~ PCAPBPF|join(" "), cwd='/root') %}
{% if PCAP_BPF_CALC['retcode'] == 0 %}
{% set PCAP_BPF_STATUS = 1 %}
{% set STENO_BPF_COMPILED = ",\\\"--filter=" + PCAP_BPF_CALC['stdout'] + "\\\"" %}
{% endif %}
{% endif %}

View File

@@ -1,11 +1,11 @@
bpf:
pcap:
description: List of BPF filters to apply to the PCAP engine.
description: List of BPF filters to apply to Stenographer.
multiline: True
forcedType: "[]string"
helpLink: bpf.html
suricata:
description: List of BPF filters to apply to Suricata. This will apply to alerts and, if enabled, to metadata and PCAP logs generated by Suricata.
description: List of BPF filters to apply to Suricata.
multiline: True
forcedType: "[]string"
helpLink: bpf.html

View File

@@ -1,16 +1,7 @@
{% from 'vars/globals.map.jinja' import GLOBALS %}
{% import_yaml 'bpf/defaults.yaml' as BPFDEFAULTS %}
{% set BPFMERGED = salt['pillar.get']('bpf', BPFDEFAULTS.bpf, merge=True) %}
{% set SURICATA_BPF_STATUS = 0 %}
{% import 'bpf/macros.jinja' as MACROS %}
{{ MACROS.remove_comments(BPFMERGED, 'suricata') }}
{% set SURICATABPF = BPFMERGED.suricata %}
{% if SURICATABPF %}
{% set SURICATA_BPF_CALC = salt['cmd.run_all']('/usr/sbin/so-bpf-compile ' ~ GLOBALS.sensor.interface ~ ' ' ~ SURICATABPF|join(" "), cwd='/root') %}
{% if SURICATA_BPF_CALC['retcode'] == 0 %}
{% set SURICATA_BPF_STATUS = 1 %}
{% endif %}
{% endif %}

View File

@@ -1,16 +1,7 @@
{% from 'vars/globals.map.jinja' import GLOBALS %}
{% import_yaml 'bpf/defaults.yaml' as BPFDEFAULTS %}
{% set BPFMERGED = salt['pillar.get']('bpf', BPFDEFAULTS.bpf, merge=True) %}
{% set ZEEK_BPF_STATUS = 0 %}
{% import 'bpf/macros.jinja' as MACROS %}
{{ MACROS.remove_comments(BPFMERGED, 'zeek') }}
{% set ZEEKBPF = BPFMERGED.zeek %}
{% if ZEEKBPF %}
{% set ZEEK_BPF_CALC = salt['cmd.run_all']('/usr/sbin/so-bpf-compile ' ~ GLOBALS.sensor.interface ~ ' ' ~ ZEEKBPF|join(" "), cwd='/root') %}
{% if ZEEK_BPF_CALC['retcode'] == 0 %}
{% set ZEEK_BPF_STATUS = 1 %}
{% endif %}
{% endif %}

View File

@@ -1,21 +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.
{% set nsm_exists = salt['file.directory_exists']('/nsm') %}
{% if nsm_exists %}
{% set nsm_total = salt['cmd.shell']('df -BG /nsm | tail -1 | awk \'{print $2}\'') %}
nsm_total:
grains.present:
- name: nsm_total
- value: {{ nsm_total }}
{% else %}
nsm_missing:
test.succeed_without_changes:
- name: /nsm does not exist, skipping grain assignment
{% endif %}

View File

@@ -4,7 +4,6 @@
{% from 'vars/globals.map.jinja' import GLOBALS %}
include:
- common.grains
- common.packages
{% if GLOBALS.role in GLOBALS.manager_roles %}
- manager.elasticsearch # needed for elastic_curl_config state

View File

@@ -29,26 +29,9 @@ fi
interface="$1"
shift
# Capture tcpdump output and exit code
tcpdump_output=$(tcpdump -i "$interface" -ddd "$@" 2>&1)
tcpdump_exit=$?
if [ $tcpdump_exit -ne 0 ]; then
echo "$tcpdump_output" >&2
exit $tcpdump_exit
fi
# Process the output, skipping the first line
echo "$tcpdump_output" | tail -n+2 | while read -r line; do
tcpdump -i $interface -ddd $@ | tail -n+2 |
while read line; do
cols=( $line )
printf "%04x%02x%02x%08x" "${cols[0]}" "${cols[1]}" "${cols[2]}" "${cols[3]}"
printf "%04x%02x%02x%08x" ${cols[0]} ${cols[1]} ${cols[2]} ${cols[3]}
done
# Check if the pipeline succeeded
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
exit 1
fi
echo ""
exit 0

View File

@@ -220,22 +220,12 @@ compare_es_versions() {
}
copy_new_files() {
# Define files to exclude from deletion (relative to their respective base directories)
local EXCLUDE_FILES=(
"salt/hypervisor/soc_hypervisor.yaml"
)
# Build rsync exclude arguments
local EXCLUDE_ARGS=()
for file in "${EXCLUDE_FILES[@]}"; do
EXCLUDE_ARGS+=(--exclude="$file")
done
# Copy new files over to the salt dir
cd $UPDATE_DIR
rsync -a salt $DEFAULT_SALT_DIR/ --delete "${EXCLUDE_ARGS[@]}"
rsync -a pillar $DEFAULT_SALT_DIR/ --delete "${EXCLUDE_ARGS[@]}"
rsync -a salt $DEFAULT_SALT_DIR/ --delete
rsync -a pillar $DEFAULT_SALT_DIR/ --delete
chown -R socore:socore $DEFAULT_SALT_DIR/
chmod 755 $DEFAULT_SALT_DIR/pillar/firewall/addfirewall.sh
cd /tmp
}
@@ -395,7 +385,7 @@ is_manager_node() {
}
is_sensor_node() {
# Check to see if this is a sensor node
# Check to see if this is a sensor (forward) node
is_single_node_grid && return 0
grep "role: so-" /etc/salt/grains | grep -E "sensor|heavynode" &> /dev/null
}
@@ -451,7 +441,8 @@ lookup_grain() {
lookup_role() {
id=$(lookup_grain id)
echo "${id##*_}"
pieces=($(echo $id | tr '_' ' '))
echo ${pieces[1]}
}
is_feature_enabled() {

View File

@@ -62,6 +62,8 @@ container_list() {
"so-soc"
"so-steno"
"so-strelka-backend"
"so-strelka-filestream"
"so-strelka-frontend"
"so-strelka-manager"
"so-suricata"
"so-telegraf"

View File

@@ -222,7 +222,6 @@ if [[ $EXCLUDE_KNOWN_ERRORS == 'Y' ]]; then
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Initialized license manager" # SOC log: before fields.status was changed to fields.licenseStatus
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|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
fi
RESULT=0
@@ -269,13 +268,6 @@ for log_file in $(cat /tmp/log_check_files); do
tail -n $RECENT_LOG_LINES $log_file > /tmp/log_check
check_for_errors
done
# Look for OOM specific errors in /var/log/messages which can lead to odd behavior / test failures
if [[ -f /var/log/messages ]]; then
status "Checking log file /var/log/messages"
if journalctl --since "24 hours ago" | grep -iE 'out of memory|oom-kill'; then
RESULT=1
fi
fi
# Cleanup temp files
rm -f /tmp/log_check_files

View File

@@ -173,7 +173,7 @@ for PCAP in $INPUT_FILES; do
status "- assigning unique identifier to import: $HASH"
pcap_data=$(pcapinfo "${PCAP}")
if ! echo "$pcap_data" | grep -q "Earliest packet time:" || echo "$pcap_data" |egrep -q "Latest packet time: 1970-01-01|Latest packet time: n/a"; then
if ! echo "$pcap_data" | grep -q "First packet time:" || echo "$pcap_data" |egrep -q "Last packet time: 1970-01-01|Last packet time: n/a"; then
status "- this PCAP file is invalid; skipping"
INVALID_PCAPS_COUNT=$((INVALID_PCAPS_COUNT + 1))
else
@@ -205,8 +205,8 @@ for PCAP in $INPUT_FILES; do
HASHES="${HASHES} ${HASH}"
fi
START=$(pcapinfo "${PCAP}" -a |grep "Earliest packet time:" | awk '{print $4}')
END=$(pcapinfo "${PCAP}" -e |grep "Latest packet time:" | awk '{print $4}')
START=$(pcapinfo "${PCAP}" -a |grep "First packet time:" | awk '{print $4}')
END=$(pcapinfo "${PCAP}" -e |grep "Last packet time:" | awk '{print $4}')
status "- found PCAP data spanning dates $START through $END"
# compare $START to $START_OLDEST

View File

@@ -15,6 +15,7 @@ elasticfleet:
logging:
zeek:
excluded:
- analyzer
- broker
- capture_loss
- cluster

View File

@@ -135,18 +135,12 @@ so-elastic-fleet-package-statefile:
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:
@@ -158,11 +152,7 @@ so-elastic-agent-grid-upgrade:
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

View File

@@ -40,7 +40,7 @@
"enabled": true,
"vars": {
"paths": [
"/opt/so/log/elasticsearch/*.json"
"/opt/so/log/elasticsearch/*.log"
]
}
},

View File

@@ -20,7 +20,7 @@
],
"data_stream.dataset": "import",
"custom": "",
"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",
"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.5.4\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.5.4\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.5.4\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": [
"import"
]

View File

@@ -2,30 +2,26 @@
# 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.
{% set GRIDNODETOKEN = salt['pillar.get']('global:fleet_grid_enrollment_token_general') -%}
{% if grains.role == 'so-heavynode' %}
{% set GRIDNODETOKEN = salt['pillar.get']('global:fleet_grid_enrollment_token_heavy') -%}
{% endif %}
{%- set GRIDNODETOKENGENERAL = salt['pillar.get']('global:fleet_grid_enrollment_token_general') -%}
{%- set GRIDNODETOKENHEAVY = salt['pillar.get']('global:fleet_grid_enrollment_token_heavy') -%}
{% set AGENT_STATUS = salt['service.available']('elastic-agent') %}
{% if not AGENT_STATUS %}
pull_agent_installer:
file.managed:
- name: /opt/so/so-elastic-agent_linux_amd64
- source: salt://elasticfleet/files/so_agent-installers/so-elastic-agent_linux_amd64
- mode: 755
- makedirs: True
{% if grains.role not in ['so-heavynode'] %}
run_installer:
cmd.run:
- name: ./so-elastic-agent_linux_amd64 -token={{ GRIDNODETOKEN }}
cmd.script:
- name: salt://elasticfleet/files/so_agent-installers/so-elastic-agent_linux_amd64
- cwd: /opt/so
- retry:
attempts: 3
interval: 20
cleanup_agent_installer:
file.absent:
- name: /opt/so/so-elastic-agent_linux_amd64
- args: -token={{ GRIDNODETOKENGENERAL }}
- retry: True
{% else %}
run_installer:
cmd.script:
- name: salt://elasticfleet/files/so_agent-installers/so-elastic-agent_linux_amd64
- cwd: /opt/so
- args: -token={{ GRIDNODETOKENHEAVY }}
- retry: True
{% endif %}
{% endif %}

View File

@@ -23,13 +23,6 @@ fi
# Define a banner to separate sections
banner="========================================================================="
fleet_api() {
local QUERYPATH=$1
shift
curl -sK /opt/so/conf/elasticsearch/curl.config -L "localhost:5601/api/fleet/${QUERYPATH}" "$@" --retry 3 --retry-delay 10 --fail 2>/dev/null
}
elastic_fleet_integration_check() {
AGENT_POLICY=$1
@@ -46,9 +39,7 @@ elastic_fleet_integration_create() {
JSON_STRING=$1
if ! fleet_api "package_policies" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -XPOST -d "$JSON_STRING"; then
return 1
fi
curl -K /opt/so/conf/elasticsearch/curl.config -L -X POST "localhost:5601/api/fleet/package_policies" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"
}
@@ -65,10 +56,7 @@ elastic_fleet_integration_remove() {
'{"packagePolicyIds":[$INTEGRATIONID]}'
)
if ! fleet_api "package_policies/delete" -XPOST -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"; then
echo "Error: Unable to delete '$NAME' from '$AGENT_POLICY'"
return 1
fi
curl -K /opt/so/conf/elasticsearch/curl.config -L -X POST "localhost:5601/api/fleet/package_policies/delete" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"
}
elastic_fleet_integration_update() {
@@ -77,9 +65,7 @@ elastic_fleet_integration_update() {
JSON_STRING=$2
if ! fleet_api "package_policies/$UPDATE_ID" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -XPUT -d "$JSON_STRING"; then
return 1
fi
curl -K /opt/so/conf/elasticsearch/curl.config -L -X PUT "localhost:5601/api/fleet/package_policies/$UPDATE_ID" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"
}
elastic_fleet_integration_policy_upgrade() {
@@ -91,83 +77,78 @@ elastic_fleet_integration_policy_upgrade() {
'{"packagePolicyIds":[$INTEGRATIONID]}'
)
if ! fleet_api "package_policies/upgrade" -XPOST -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"; then
return 1
fi
curl -K /opt/so/conf/elasticsearch/curl.config -L -X POST "localhost:5601/api/fleet/package_policies/upgrade" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"
}
elastic_fleet_package_version_check() {
PACKAGE=$1
if output=$(fleet_api "epm/packages/$PACKAGE"); then
echo "$output" | jq -r '.item.version'
else
echo "Error: Failed to get current package version for '$PACKAGE'"
return 1
fi
curl -s -K /opt/so/conf/elasticsearch/curl.config -b "sid=$SESSIONCOOKIE" -L -X GET "localhost:5601/api/fleet/epm/packages/$PACKAGE" | jq -r '.item.version'
}
elastic_fleet_package_latest_version_check() {
PACKAGE=$1
if output=$(fleet_api "epm/packages/$PACKAGE"); then
if output=$(curl -s -K /opt/so/conf/elasticsearch/curl.config -b "sid=$SESSIONCOOKIE" -L -X GET "localhost:5601/api/fleet/epm/packages/$PACKAGE" --fail); then
if version=$(jq -e -r '.item.latestVersion' <<< $output); then
echo "$version"
fi
else
echo "Error: Failed to get latest version for '$PACKAGE'"
return 1
echo "Error: Failed to get latest version for $PACKAGE"
fi
}
elastic_fleet_package_install() {
PKG=$1
VERSION=$2
if ! fleet_api "epm/packages/$PKG/$VERSION" -XPOST -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d '{"force":true}'; then
return 1
fi
curl -s -K /opt/so/conf/elasticsearch/curl.config -b "sid=$SESSIONCOOKIE" -L -X POST -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d '{"force":true}' "localhost:5601/api/fleet/epm/packages/$PKG/$VERSION"
}
elastic_fleet_bulk_package_install() {
BULK_PKG_LIST=$1
if ! fleet_api "epm/packages/_bulk" -XPOST -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d@$BULK_PKG_LIST; then
return 1
fi
curl -s -K /opt/so/conf/elasticsearch/curl.config -b "sid=$SESSIONCOOKIE" -L -X POST -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d@$1 "localhost:5601/api/fleet/epm/packages/_bulk"
}
elastic_fleet_package_is_installed() {
PACKAGE=$1
curl -s -K /opt/so/conf/elasticsearch/curl.config -b "sid=$SESSIONCOOKIE" -L -X GET -H 'kbn-xsrf: true' "localhost:5601/api/fleet/epm/packages/$PACKAGE" | jq -r '.item.status'
}
elastic_fleet_installed_packages() {
if ! fleet_api "epm/packages/installed?perPage=500"; then
return 1
fi
curl -s -K /opt/so/conf/elasticsearch/curl.config -b "sid=$SESSIONCOOKIE" -L -X GET -H 'kbn-xsrf: true' -H 'Content-Type: application/json' "localhost:5601/api/fleet/epm/packages/installed?perPage=500"
}
elastic_fleet_agent_policy_ids() {
if output=$(fleet_api "agent_policies"); then
echo "$output" | jq -r .items[].id
else
curl -s -K /opt/so/conf/elasticsearch/curl.config -b "sid=$SESSIONCOOKIE" -L -X GET "localhost:5601/api/fleet/agent_policies" | jq -r .items[].id
if [ $? -ne 0 ]; then
echo "Error: Failed to retrieve agent policies."
return 1
exit 1
fi
}
elastic_fleet_agent_policy_names() {
curl -s -K /opt/so/conf/elasticsearch/curl.config -b "sid=$SESSIONCOOKIE" -L -X GET "localhost:5601/api/fleet/agent_policies" | jq -r .items[].name
if [ $? -ne 0 ]; then
echo "Error: Failed to retrieve agent policies."
exit 1
fi
}
elastic_fleet_integration_policy_names() {
AGENT_POLICY=$1
if output=$(fleet_api "agent_policies/$AGENT_POLICY"); then
echo "$output" | jq -r .item.package_policies[].name
else
curl -s -K /opt/so/conf/elasticsearch/curl.config -b "sid=$SESSIONCOOKIE" -L -X GET "localhost:5601/api/fleet/agent_policies/$AGENT_POLICY" | jq -r .item.package_policies[].name
if [ $? -ne 0 ]; then
echo "Error: Failed to retrieve integrations for '$AGENT_POLICY'."
return 1
exit 1
fi
}
elastic_fleet_integration_policy_package_name() {
AGENT_POLICY=$1
INTEGRATION=$2
if output=$(fleet_api "agent_policies/$AGENT_POLICY"); then
echo "$output" | jq -r --arg INTEGRATION "$INTEGRATION" '.item.package_policies[] | select(.name==$INTEGRATION)| .package.name'
else
curl -s -K /opt/so/conf/elasticsearch/curl.config -b "sid=$SESSIONCOOKIE" -L -X GET "localhost:5601/api/fleet/agent_policies/$AGENT_POLICY" | jq -r --arg INTEGRATION "$INTEGRATION" '.item.package_policies[] | select(.name==$INTEGRATION)| .package.name'
if [ $? -ne 0 ]; then
echo "Error: Failed to retrieve package name for '$INTEGRATION' in '$AGENT_POLICY'."
return 1
exit 1
fi
}
@@ -175,32 +156,32 @@ elastic_fleet_integration_policy_package_version() {
AGENT_POLICY=$1
INTEGRATION=$2
if output=$(fleet_api "agent_policies/$AGENT_POLICY"); then
if version=$(jq -e -r --arg INTEGRATION "$INTEGRATION" '.item.package_policies[] | select(.name==$INTEGRATION)| .package.version' <<< "$output"); then
if output=$(curl -s -K /opt/so/conf/elasticsearch/curl.config -L -X GET "localhost:5601/api/fleet/agent_policies/$AGENT_POLICY" --fail); then
if version=$(jq -e -r --arg INTEGRATION "$INTEGRATION" '.item.package_policies[] | select(.name==$INTEGRATION)| .package.version' <<< $output); then
echo "$version"
fi
else
echo "Error: Failed to retrieve integration version for '$INTEGRATION' in policy '$AGENT_POLICY'"
return 1
echo "Error: Failed to retrieve agent policy $AGENT_POLICY"
exit 1
fi
}
elastic_fleet_integration_id() {
AGENT_POLICY=$1
INTEGRATION=$2
if output=$(fleet_api "agent_policies/$AGENT_POLICY"); then
echo "$output" | jq -r --arg INTEGRATION "$INTEGRATION" '.item.package_policies[] | select(.name==$INTEGRATION)| .id'
else
curl -s -K /opt/so/conf/elasticsearch/curl.config -b "sid=$SESSIONCOOKIE" -L -X GET "localhost:5601/api/fleet/agent_policies/$AGENT_POLICY" | jq -r --arg INTEGRATION "$INTEGRATION" '.item.package_policies[] | select(.name==$INTEGRATION)| .id'
if [ $? -ne 0 ]; then
echo "Error: Failed to retrieve integration ID for '$INTEGRATION' in '$AGENT_POLICY'."
return 1
exit 1
fi
}
elastic_fleet_integration_policy_dryrun_upgrade() {
INTEGRATION_ID=$1
if ! fleet_api "package_policies/upgrade/dryrun" -H "Content-Type: application/json" -H 'kbn-xsrf: true' -XPOST -d "{\"packagePolicyIds\":[\"$INTEGRATION_ID\"]}"; then
curl -s -K /opt/so/conf/elasticsearch/curl.config -b "sid=$SESSIONCOOKIE" -H "Content-Type: application/json" -H 'kbn-xsrf: true' -L -X POST "localhost:5601/api/fleet/package_policies/upgrade/dryrun" -d "{\"packagePolicyIds\":[\"$INTEGRATION_ID\"]}"
if [ $? -ne 0 ]; then
echo "Error: Failed to complete dry run for '$INTEGRATION_ID'."
return 1
exit 1
fi
}
@@ -219,8 +200,15 @@ elastic_fleet_policy_create() {
'{"name": $NAME,"id":$NAME,"description":$DESC,"namespace":"default","monitoring_enabled":["logs"],"inactivity_timeout":$TIMEOUT,"has_fleet_server":$FLEETSERVER}'
)
# Create Fleet Policy
if ! fleet_api "agent_policies" -XPOST -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"; then
return 1
fi
curl -K /opt/so/conf/elasticsearch/curl.config -L -X POST "localhost:5601/api/fleet/agent_policies" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"
}
elastic_fleet_policy_update() {
POLICYID=$1
JSON_STRING=$2
curl -K /opt/so/conf/elasticsearch/curl.config -L -X PUT "localhost:5601/api/fleet/agent_policies/$POLICYID" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"
}

View File

@@ -8,7 +8,6 @@
. /usr/sbin/so-elastic-fleet-common
ERROR=false
# Manage Elastic Defend Integration for Initial Endpoints Policy
for INTEGRATION in /opt/so/conf/elastic-fleet/integrations/elastic-defend/*.json
do
@@ -16,20 +15,9 @@ do
elastic_fleet_integration_check "endpoints-initial" "$INTEGRATION"
if [ -n "$INTEGRATION_ID" ]; then
printf "\n\nIntegration $NAME exists - Upgrading integration policy\n"
if ! elastic_fleet_integration_policy_upgrade "$INTEGRATION_ID"; then
echo -e "\nFailed to upgrade integration policy for ${INTEGRATION##*/}"
ERROR=true
continue
fi
elastic_fleet_integration_policy_upgrade "$INTEGRATION_ID"
else
printf "\n\nIntegration does not exist - Creating integration\n"
if ! elastic_fleet_integration_create "@$INTEGRATION"; then
echo -e "\nFailed to create integration for ${INTEGRATION##*/}"
ERROR=true
continue
fi
elastic_fleet_integration_create "@$INTEGRATION"
fi
done
if [[ "$ERROR" == "true" ]]; then
exit 1
fi

View File

@@ -25,9 +25,5 @@ for POLICYNAME in $POLICY; do
.name = $name' /opt/so/conf/elastic-fleet/integrations/fleet-server/fleet-server.json)
# Now update the integration policy using the modified JSON
if ! elastic_fleet_integration_update "$INTEGRATION_ID" "$UPDATED_INTEGRATION_POLICY"; then
# exit 1 on failure to update fleet integration policies, let salt handle retries
echo "Failed to update $POLICYNAME.."
exit 1
fi
elastic_fleet_integration_update "$INTEGRATION_ID" "$UPDATED_INTEGRATION_POLICY"
done

View File

@@ -13,10 +13,11 @@ if [ ! -f /opt/so/state/eaintegrations.txt ]; then
/usr/sbin/so-elastic-fleet-package-upgrade
# Second, update Fleet Server policies
/usr/sbin/so-elastic-fleet-integration-policy-elastic-fleet-server
/sbin/so-elastic-fleet-integration-policy-elastic-fleet-server
# Third, configure Elastic Defend Integration seperately
/usr/sbin/so-elastic-fleet-integration-policy-elastic-defend
# Initial Endpoints
for INTEGRATION in /opt/so/conf/elastic-fleet/integrations/endpoints-initial/*.json
do
@@ -24,18 +25,10 @@ if [ ! -f /opt/so/state/eaintegrations.txt ]; then
elastic_fleet_integration_check "endpoints-initial" "$INTEGRATION"
if [ -n "$INTEGRATION_ID" ]; then
printf "\n\nIntegration $NAME exists - Updating integration\n"
if ! elastic_fleet_integration_update "$INTEGRATION_ID" "@$INTEGRATION"; then
echo -e "\nFailed to update integration for ${INTEGRATION##*/}"
RETURN_CODE=1
continue
fi
elastic_fleet_integration_update "$INTEGRATION_ID" "@$INTEGRATION"
else
printf "\n\nIntegration does not exist - Creating integration\n"
if ! elastic_fleet_integration_create "@$INTEGRATION"; then
echo -e "\nFailed to create integration for ${INTEGRATION##*/}"
RETURN_CODE=1
continue
fi
elastic_fleet_integration_create "@$INTEGRATION"
fi
done
@@ -46,18 +39,10 @@ if [ ! -f /opt/so/state/eaintegrations.txt ]; then
elastic_fleet_integration_check "so-grid-nodes_general" "$INTEGRATION"
if [ -n "$INTEGRATION_ID" ]; then
printf "\n\nIntegration $NAME exists - Updating integration\n"
if ! elastic_fleet_integration_update "$INTEGRATION_ID" "@$INTEGRATION"; then
echo -e "\nFailed to update integration for ${INTEGRATION##*/}"
RETURN_CODE=1
continue
fi
elastic_fleet_integration_update "$INTEGRATION_ID" "@$INTEGRATION"
else
printf "\n\nIntegration does not exist - Creating integration\n"
if ! elastic_fleet_integration_create "@$INTEGRATION"; then
echo -e "\nFailed to create integration for ${INTEGRATION##*/}"
RETURN_CODE=1
continue
fi
elastic_fleet_integration_create "@$INTEGRATION"
fi
done
if [[ "$RETURN_CODE" != "1" ]]; then
@@ -71,19 +56,11 @@ if [ ! -f /opt/so/state/eaintegrations.txt ]; then
elastic_fleet_integration_check "so-grid-nodes_heavy" "$INTEGRATION"
if [ -n "$INTEGRATION_ID" ]; then
printf "\n\nIntegration $NAME exists - Updating integration\n"
if ! elastic_fleet_integration_update "$INTEGRATION_ID" "@$INTEGRATION"; then
echo -e "\nFailed to update integration for ${INTEGRATION##*/}"
RETURN_CODE=1
continue
fi
elastic_fleet_integration_update "$INTEGRATION_ID" "@$INTEGRATION"
else
printf "\n\nIntegration does not exist - Creating integration\n"
if [ "$NAME" != "elasticsearch-logs" ]; then
if ! elastic_fleet_integration_create "@$INTEGRATION"; then
echo -e "\nFailed to create integration for ${INTEGRATION##*/}"
RETURN_CODE=1
continue
fi
elastic_fleet_integration_create "@$INTEGRATION"
fi
fi
done
@@ -100,19 +77,11 @@ if [ ! -f /opt/so/state/eaintegrations.txt ]; then
elastic_fleet_integration_check "$FLEET_POLICY" "$INTEGRATION"
if [ -n "$INTEGRATION_ID" ]; then
printf "\n\nIntegration $NAME exists - Updating integration\n"
if ! elastic_fleet_integration_update "$INTEGRATION_ID" "@$INTEGRATION"; then
echo -e "\nFailed to update integration for ${INTEGRATION##*/}"
RETURN_CODE=1
continue
fi
elastic_fleet_integration_update "$INTEGRATION_ID" "@$INTEGRATION"
else
printf "\n\nIntegration does not exist - Creating integration\n"
if [ "$NAME" != "elasticsearch-logs" ]; then
if ! elastic_fleet_integration_create "@$INTEGRATION"; then
echo -e "\nFailed to create integration for ${INTEGRATION##*/}"
RETURN_CODE=1
continue
fi
elastic_fleet_integration_create "@$INTEGRATION"
fi
fi
fi

View File

@@ -24,18 +24,12 @@ fi
default_packages=({% for pkg in SUPPORTED_PACKAGES %}"{{ pkg }}"{% if not loop.last %} {% endif %}{% endfor %})
ERROR=false
for AGENT_POLICY in $agent_policies; do
if ! integrations=$(elastic_fleet_integration_policy_names "$AGENT_POLICY"); then
# this script upgrades default integration packages, exit 1 and let salt handle retrying
exit 1
fi
integrations=$(elastic_fleet_integration_policy_names "$AGENT_POLICY")
for INTEGRATION in $integrations; do
if ! [[ "$INTEGRATION" == "elastic-defend-endpoints" ]] && ! [[ "$INTEGRATION" == "fleet_server-"* ]]; then
# Get package name so we know what package to look for when checking the current and latest available version
if ! PACKAGE_NAME=$(elastic_fleet_integration_policy_package_name "$AGENT_POLICY" "$INTEGRATION"); then
exit 1
fi
PACKAGE_NAME=$(elastic_fleet_integration_policy_package_name "$AGENT_POLICY" "$INTEGRATION")
{%- if not AUTO_UPGRADE_INTEGRATIONS %}
if [[ " ${default_packages[@]} " =~ " $PACKAGE_NAME " ]]; then
{%- endif %}
@@ -54,9 +48,7 @@ for AGENT_POLICY in $agent_policies; do
fi
# Get integration ID
if ! INTEGRATION_ID=$(elastic_fleet_integration_id "$AGENT_POLICY" "$INTEGRATION"); then
exit 1
fi
INTEGRATION_ID=$(elastic_fleet_integration_id "$AGENT_POLICY" "$INTEGRATION")
if [[ "$PACKAGE_VERSION" != "$AVAILABLE_VERSION" ]]; then
# Dry run of the upgrade
@@ -64,23 +56,20 @@ for AGENT_POLICY in $agent_policies; do
echo "Current $PACKAGE_NAME package version ($PACKAGE_VERSION) is not the same as the latest available package ($AVAILABLE_VERSION)..."
echo "Upgrading $INTEGRATION..."
echo "Starting dry run..."
if ! DRYRUN_OUTPUT=$(elastic_fleet_integration_policy_dryrun_upgrade "$INTEGRATION_ID"); then
exit 1
fi
DRYRUN_OUTPUT=$(elastic_fleet_integration_policy_dryrun_upgrade "$INTEGRATION_ID")
DRYRUN_ERRORS=$(echo "$DRYRUN_OUTPUT" | jq .[].hasErrors)
# If no errors with dry run, proceed with actual upgrade
if [[ "$DRYRUN_ERRORS" == "false" ]]; then
echo "No errors detected. Proceeding with upgrade..."
if ! elastic_fleet_integration_policy_upgrade "$INTEGRATION_ID"; then
elastic_fleet_integration_policy_upgrade "$INTEGRATION_ID"
if [ $? -ne 0 ]; then
echo "Error: Upgrade failed for $PACKAGE_NAME with integration ID '$INTEGRATION_ID'."
ERROR=true
continue
exit 1
fi
else
echo "Errors detected during dry run for $PACKAGE_NAME policy upgrade..."
ERROR=true
continue
exit 1
fi
fi
{%- if not AUTO_UPGRADE_INTEGRATIONS %}
@@ -89,7 +78,4 @@ for AGENT_POLICY in $agent_policies; do
fi
done
done
if [[ "$ERROR" == "true" ]]; then
exit 1
fi
echo

View File

@@ -62,17 +62,9 @@ default_packages=({% for pkg in SUPPORTED_PACKAGES %}"{{ pkg }}"{% if not loop.l
in_use_integrations=()
for AGENT_POLICY in $agent_policies; do
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.
echo "Skipping $AGENT_POLICY.. "
continue
fi
integrations=$(elastic_fleet_integration_policy_names "$AGENT_POLICY")
for INTEGRATION in $integrations; do
if ! PACKAGE_NAME=$(elastic_fleet_integration_policy_package_name "$AGENT_POLICY" "$INTEGRATION"); then
echo "Not adding $INTEGRATION, couldn't get package name"
continue
fi
PACKAGE_NAME=$(elastic_fleet_integration_policy_package_name "$AGENT_POLICY" "$INTEGRATION")
# non-default integrations that are in-use in any policy
if ! [[ " ${default_packages[@]} " =~ " $PACKAGE_NAME " ]]; then
in_use_integrations+=("$PACKAGE_NAME")
@@ -168,11 +160,7 @@ if [[ -f $STATE_FILE_SUCCESS ]]; then
for file in "${pkg_filename}_"*.json; do
[ -e "$file" ] || continue
if ! elastic_fleet_bulk_package_install $file >> $BULK_INSTALL_OUTPUT; then
# integrations loaded my this script are non-essential and shouldn't cause exit, skip them for now next highstate run can retry
echo "Failed to complete a chunk of bulk package installs -- $file "
continue
fi
elastic_fleet_bulk_package_install $file >> $BULK_INSTALL_OUTPUT
done
# cleanup any temp files for chunked package install
rm -f ${pkg_filename}_*.json $BULK_INSTALL_PACKAGE_LIST
@@ -180,9 +168,8 @@ if [[ -f $STATE_FILE_SUCCESS ]]; then
echo "Elastic integrations don't appear to need installation/updating..."
fi
# Write out file for generating index/component/ilm templates
if latest_installed_package_list=$(elastic_fleet_installed_packages); then
latest_installed_package_list=$(elastic_fleet_installed_packages)
echo $latest_installed_package_list | jq '[.items[] | {name: .name, es_index_patterns: .dataStreams}]' > $PACKAGE_COMPONENTS
fi
if retry 3 1 "so-elasticsearch-query / --fail --output /dev/null"; then
# Refresh installed component template list
latest_component_templates_list=$(so-elasticsearch-query _component_template | jq '.component_templates[] | .name' | jq -s '.')

View File

@@ -15,21 +15,8 @@ if ! is_manager_node; then
fi
function update_logstash_outputs() {
if logstash_policy=$(curl -K /opt/so/conf/elasticsearch/curl.config -L "http://localhost:5601/api/fleet/outputs/so-manager_logstash" --retry 3 --retry-delay 10 --fail 2>/dev/null); then
SSL_CONFIG=$(echo "$logstash_policy" | jq -r '.item.ssl')
if SECRETS=$(echo "$logstash_policy" | jq -er '.item.secrets' 2>/dev/null); then
JSON_STRING=$(jq -n \
--arg UPDATEDLIST "$NEW_LIST_JSON" \
--argjson SECRETS "$SECRETS" \
--argjson SSL_CONFIG "$SSL_CONFIG" \
'{"name":"grid-logstash","type":"logstash","hosts": $UPDATEDLIST,"is_default":true,"is_default_monitoring":true,"config_yaml":"","ssl": $SSL_CONFIG,"secrets": $SECRETS}')
else
JSON_STRING=$(jq -n \
--arg UPDATEDLIST "$NEW_LIST_JSON" \
--argjson SSL_CONFIG "$SSL_CONFIG" \
'{"name":"grid-logstash","type":"logstash","hosts": $UPDATEDLIST,"is_default":true,"is_default_monitoring":true,"config_yaml":"","ssl": $SSL_CONFIG}')
fi
fi
# Generate updated JSON payload
JSON_STRING=$(jq -n --arg UPDATEDLIST $NEW_LIST_JSON '{"name":"grid-logstash","type":"logstash","hosts": $UPDATEDLIST,"is_default":true,"is_default_monitoring":true,"config_yaml":""}')
# Update Logstash Outputs
curl -K /opt/so/conf/elasticsearch/curl.config -L -X PUT "localhost:5601/api/fleet/outputs/so-manager_logstash" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING" | jq

View File

@@ -10,16 +10,8 @@
{%- for PACKAGE in SUPPORTED_PACKAGES %}
echo "Setting up {{ PACKAGE }} package..."
if VERSION=$(elastic_fleet_package_version_check "{{ PACKAGE }}"); then
if ! elastic_fleet_package_install "{{ PACKAGE }}" "$VERSION"; then
# packages loaded by this script should never fail to install and REQUIRED before an installation of SO can be considered successful
echo -e "\nERROR: Failed to install default integration package -- $PACKAGE $VERSION"
exit 1
fi
else
echo -e "\nERROR: Failed to get version information for integration $PACKAGE"
exit 1
fi
VERSION=$(elastic_fleet_package_version_check "{{ PACKAGE }}")
elastic_fleet_package_install "{{ PACKAGE }}" "$VERSION"
echo
{%- endfor %}
echo

View File

@@ -10,15 +10,8 @@
{%- for PACKAGE in SUPPORTED_PACKAGES %}
echo "Upgrading {{ PACKAGE }} package..."
if VERSION=$(elastic_fleet_package_latest_version_check "{{ PACKAGE }}"); then
if ! elastic_fleet_package_install "{{ PACKAGE }}" "$VERSION"; then
# exit 1 on failure to upgrade a default package, allow salt to handle retries
echo -e "\nERROR: Failed to upgrade $PACKAGE to version: $VERSION"
exit 1
fi
else
echo -e "\nERROR: Failed to get version information for integration $PACKAGE"
fi
VERSION=$(elastic_fleet_package_latest_version_check "{{ PACKAGE }}")
elastic_fleet_package_install "{{ PACKAGE }}" "$VERSION"
echo
{%- endfor %}
echo

View File

@@ -23,17 +23,18 @@ if [[ "$RETURN_CODE" != "0" ]]; then
exit 1
fi
ALIASES=(.fleet-servers .fleet-policies-leader .fleet-policies .fleet-agents .fleet-artifacts .fleet-enrollment-api-keys .kibana_ingest)
for ALIAS in "${ALIASES[@]}"; do
ALIASES=".fleet-servers .fleet-policies-leader .fleet-policies .fleet-agents .fleet-artifacts .fleet-enrollment-api-keys .kibana_ingest"
for ALIAS in ${ALIASES}
do
# Get all concrete indices from alias
if INDXS_RAW=$(curl -sK /opt/so/conf/kibana/curl.config -s -k -L -H "Content-Type: application/json" "https://localhost:9200/_resolve/index/${ALIAS}" --fail 2>/dev/null); then
INDXS=$(echo "$INDXS_RAW" | jq -r '.aliases[].indices[]')
INDXS=$(curl -K /opt/so/conf/kibana/curl.config -s -k -L -H "Content-Type: application/json" "https://localhost:9200/_resolve/index/${ALIAS}" | jq -r '.aliases[].indices[]')
# Delete all resolved indices
for INDX in ${INDXS}; do
for INDX in ${INDXS}
do
status "Deleting $INDX"
curl -K /opt/so/conf/kibana/curl.config -s -k -L -H "Content-Type: application/json" "https://localhost:9200/${INDX}" -XDELETE
done
fi
done
# Restarting Kibana...
@@ -50,61 +51,22 @@ if [[ "$RETURN_CODE" != "0" ]]; then
fi
printf "\n### Create ES Token ###\n"
if ESTOKEN_RAW=$(fleet_api "service_tokens" -XPOST -H 'kbn-xsrf: true' -H 'Content-Type: application/json'); then
ESTOKEN=$(echo "$ESTOKEN_RAW" | jq -r .value)
else
echo -e "\nFailed to create ES token..."
exit 1
fi
ESTOKEN=$(curl -K /opt/so/conf/elasticsearch/curl.config -L -X POST "localhost:5601/api/fleet/service_tokens" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' | jq -r .value)
### Create Outputs, Fleet Policy and Fleet URLs ###
# Create the Manager Elasticsearch Output first and set it as the default output
printf "\nAdd Manager Elasticsearch Output...\n"
ESCACRT=$(openssl x509 -in "$INTCA" -outform DER | sha256sum | cut -d' ' -f1 | tr '[:lower:]' '[:upper:]')
JSON_STRING=$(jq -n \
ESCACRT=$(openssl x509 -in $INTCA)
JSON_STRING=$( jq -n \
--arg ESCACRT "$ESCACRT" \
'{"name":"so-manager_elasticsearch","id":"so-manager_elasticsearch","type":"elasticsearch","hosts":["https://{{ GLOBALS.manager_ip }}:9200","https://{{ GLOBALS.manager }}:9200"],"is_default":false,"is_default_monitoring":false,"config_yaml":"","ca_trusted_fingerprint": $ESCACRT}')
if ! fleet_api "outputs" -XPOST -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"; then
echo -e "\nFailed to create so-elasticsearch_manager policy..."
exit 1
fi
'{"name":"so-manager_elasticsearch","id":"so-manager_elasticsearch","type":"elasticsearch","hosts":["https://{{ GLOBALS.manager_ip }}:9200","https://{{ GLOBALS.manager }}:9200"],"is_default":true,"is_default_monitoring":true,"config_yaml":"","ssl":{"certificate_authorities": [$ESCACRT]}}' )
curl -K /opt/so/conf/elasticsearch/curl.config -L -X POST "localhost:5601/api/fleet/outputs" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"
printf "\n\n"
# so-manager_elasticsearch should exist and be disabled. Now update it before checking its the only default policy
MANAGER_OUTPUT_ENABLED=$(echo "$JSON_STRING" | jq 'del(.id) | .is_default = true | .is_default_monitoring = true')
if ! curl -sK /opt/so/conf/elasticsearch/curl.config -L -X PUT "localhost:5601/api/fleet/outputs/so-manager_elasticsearch" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$MANAGER_OUTPUT_ENABLED"; then
echo -e "\n failed to update so-manager_elasticsearch"
exit 1
fi
# At this point there should only be two policies. fleet-default-output & so-manager_elasticsearch
status "Verifying so-manager_elasticsearch policy is configured as the current default"
# Grab the fleet-default-output policy instead of so-manager_elasticsearch, because a weird state can exist where both fleet-default-output & so-elasticsearch_manager can be set as the active default output for logs / metrics. Resulting in logs not ingesting on import/eval nodes
if DEFAULTPOLICY=$(fleet_api "outputs/fleet-default-output"); then
fleet_default=$(echo "$DEFAULTPOLICY" | jq -er '.item.is_default')
fleet_default_monitoring=$(echo "$DEFAULTPOLICY" | jq -er '.item.is_default_monitoring')
# Check that fleet-default-output isn't configured as a default for anything ( both variables return false )
if [[ $fleet_default == "false" ]] && [[ $fleet_default_monitoring == "false" ]]; then
echo -e "\nso-manager_elasticsearch is configured as the current default policy..."
else
echo -e "\nVerification of so-manager_elasticsearch policy failed... The default 'fleet-default-output' output is still active..."
exit 1
fi
else
# fleet-output-policy is created automatically by fleet when started. Should always exist on any installation type
echo -e "\nDefault fleet-default-output policy doesn't exist...\n"
exit 1
fi
# Create the Manager Fleet Server Host Agent Policy
# This has to be done while the Elasticsearch Output is set to the default Output
printf "Create Manager Fleet Server Policy...\n"
if ! elastic_fleet_policy_create "FleetServer_{{ GLOBALS.hostname }}" "Fleet Server - {{ GLOBALS.hostname }}" "false" "120"; then
echo -e "\n Failed to create Manager fleet server policy..."
exit 1
fi
elastic_fleet_policy_create "FleetServer_{{ GLOBALS.hostname }}" "Fleet Server - {{ GLOBALS.hostname }}" "false" "120"
# Modify the default integration policy to update the policy_id with the correct naming
UPDATED_INTEGRATION_POLICY=$(jq --arg policy_id "FleetServer_{{ GLOBALS.hostname }}" --arg name "fleet_server-{{ GLOBALS.hostname }}" '
@@ -112,10 +74,7 @@ UPDATED_INTEGRATION_POLICY=$(jq --arg policy_id "FleetServer_{{ GLOBALS.hostname
.name = $name' /opt/so/conf/elastic-fleet/integrations/fleet-server/fleet-server.json)
# Add the Fleet Server Integration to the new Fleet Policy
if ! elastic_fleet_integration_create "$UPDATED_INTEGRATION_POLICY"; then
echo -e "\nFailed to create Fleet server integration for Manager.."
exit 1
fi
elastic_fleet_integration_create "$UPDATED_INTEGRATION_POLICY"
# Now we can create the Logstash Output and set it to to be the default Output
printf "\n\nCreate Logstash Output Config if node is not an Import or Eval install\n"
@@ -127,12 +86,9 @@ JSON_STRING=$( jq -n \
--arg LOGSTASHCRT "$LOGSTASHCRT" \
--arg LOGSTASHKEY "$LOGSTASHKEY" \
--arg LOGSTASHCA "$LOGSTASHCA" \
'{"name":"grid-logstash","is_default":true,"is_default_monitoring":true,"id":"so-manager_logstash","type":"logstash","hosts":["{{ GLOBALS.manager_ip }}:5055", "{{ GLOBALS.manager }}:5055"],"config_yaml":"","ssl":{"certificate": $LOGSTASHCRT,"certificate_authorities":[ $LOGSTASHCA ]},"secrets":{"ssl":{"key": $LOGSTASHKEY }},"proxy_id":null}'
'{"name":"grid-logstash","is_default":true,"is_default_monitoring":true,"id":"so-manager_logstash","type":"logstash","hosts":["{{ GLOBALS.manager_ip }}:5055", "{{ GLOBALS.manager }}:5055"],"config_yaml":"","ssl":{"certificate": $LOGSTASHCRT,"key": $LOGSTASHKEY,"certificate_authorities":[ $LOGSTASHCA ]},"proxy_id":null}'
)
if ! fleet_api "outputs" -XPOST -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"; then
echo -e "\nFailed to create logstash fleet output"
exit 1
fi
curl -K /opt/so/conf/elasticsearch/curl.config -L -X POST "localhost:5601/api/fleet/outputs" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"
printf "\n\n"
{%- endif %}
@@ -150,10 +106,7 @@ else
fi
## This array replaces whatever URLs are currently configured
if ! fleet_api "fleet_server_hosts" -XPOST -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"; then
echo -e "\nFailed to add manager fleet URL"
exit 1
fi
curl -K /opt/so/conf/elasticsearch/curl.config -L -X POST "localhost:5601/api/fleet/fleet_server_hosts" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"
printf "\n\n"
### Create Policies & Associated Integration Configuration ###
@@ -164,22 +117,13 @@ printf "\n\n"
/usr/sbin/so-elasticsearch-templates-load
# Initial Endpoints Policy
if ! elastic_fleet_policy_create "endpoints-initial" "Initial Endpoint Policy" "false" "1209600"; then
echo -e "\nFailed to create endpoints-initial policy..."
exit 1
fi
elastic_fleet_policy_create "endpoints-initial" "Initial Endpoint Policy" "false" "1209600"
# Grid Nodes - General Policy
if ! elastic_fleet_policy_create "so-grid-nodes_general" "SO Grid Nodes - General Purpose" "false" "1209600"; then
echo -e "\nFailed to create so-grid-nodes_general policy..."
exit 1
fi
elastic_fleet_policy_create "so-grid-nodes_general" "SO Grid Nodes - General Purpose" "false" "1209600"
# Grid Nodes - Heavy Node Policy
if ! elastic_fleet_policy_create "so-grid-nodes_heavy" "SO Grid Nodes - Heavy Node" "false" "1209600"; then
echo -e "\nFailed to create so-grid-nodes_heavy policy..."
exit 1
fi
elastic_fleet_policy_create "so-grid-nodes_heavy" "SO Grid Nodes - Heavy Node" "false" "1209600"
# Load Integrations for default policies
so-elastic-fleet-integration-policy-load
@@ -191,34 +135,14 @@ JSON_STRING=$( jq -n \
'{"name":$NAME,"host":$URL,"is_default":true}'
)
if ! fleet_api "agent_download_sources" -XPOST -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"; then
echo -e "\nFailed to update Elastic Agent artifact URL"
exit 1
fi
curl -K /opt/so/conf/elasticsearch/curl.config -L -X POST "localhost:5601/api/fleet/agent_download_sources" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING"
### Finalization ###
# Query for Enrollment Tokens for default policies
if ENDPOINTSENROLLMENTOKEN_RAW=$(fleet_api "enrollment_api_keys" -H 'kbn-xsrf: true' -H 'Content-Type: application/json'); then
ENDPOINTSENROLLMENTOKEN=$(echo "$ENDPOINTSENROLLMENTOKEN_RAW" | jq .list | jq -r -c '.[] | select(.policy_id | contains("endpoints-initial")) | .api_key')
else
echo -e "\nFailed to query for Endpoints enrollment token"
exit 1
fi
if GRIDNODESENROLLMENTOKENGENERAL_RAW=$(fleet_api "enrollment_api_keys" -H 'kbn-xsrf: true' -H 'Content-Type: application/json'); then
GRIDNODESENROLLMENTOKENGENERAL=$(echo "$GRIDNODESENROLLMENTOKENGENERAL_RAW" | jq .list | jq -r -c '.[] | select(.policy_id | contains("so-grid-nodes_general")) | .api_key')
else
echo -e "\nFailed to query for Grid nodes - General enrollment token"
exit 1
fi
if GRIDNODESENROLLMENTOKENHEAVY_RAW=$(fleet_api "enrollment_api_keys" -H 'kbn-xsrf: true' -H 'Content-Type: application/json'); then
GRIDNODESENROLLMENTOKENHEAVY=$(echo "$GRIDNODESENROLLMENTOKENHEAVY_RAW" | jq .list | jq -r -c '.[] | select(.policy_id | contains("so-grid-nodes_heavy")) | .api_key')
else
echo -e "\nFailed to query for Grid nodes - Heavy enrollment token"
exit 1
fi
ENDPOINTSENROLLMENTOKEN=$(curl -K /opt/so/conf/elasticsearch/curl.config -L "localhost:5601/api/fleet/enrollment_api_keys" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' | jq .list | jq -r -c '.[] | select(.policy_id | contains("endpoints-initial")) | .api_key')
GRIDNODESENROLLMENTOKENGENERAL=$(curl -K /opt/so/conf/elasticsearch/curl.config -L "localhost:5601/api/fleet/enrollment_api_keys" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' | jq .list | jq -r -c '.[] | select(.policy_id | contains("so-grid-nodes_general")) | .api_key')
GRIDNODESENROLLMENTOKENHEAVY=$(curl -K /opt/so/conf/elasticsearch/curl.config -L "localhost:5601/api/fleet/enrollment_api_keys" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' | jq .list | jq -r -c '.[] | select(.policy_id | contains("so-grid-nodes_heavy")) | .api_key')
# Store needed data in minion pillar
pillar_file=/opt/so/saltstack/local/pillar/minions/{{ GLOBALS.minion_id }}.sls

View File

@@ -1,6 +1,6 @@
elasticsearch:
enabled: false
version: 8.18.8
version: 8.18.6
index_clean: true
config:
action:
@@ -1991,70 +1991,6 @@ elasticsearch:
set_priority:
priority: 50
min_age: 30d
so-logs-elasticsearch_x_server:
index_sorting: false
index_template:
composed_of:
- logs-elasticsearch.server@package
- logs-elasticsearch.server@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:
- logs-elasticsearch.server@custom
index_patterns:
- logs-elasticsearch.server-*
priority: 501
template:
mappings:
_meta:
managed: true
managed_by: security_onion
package:
name: elastic_agent
settings:
index:
lifecycle:
name: so-logs-elasticsearch.server-logs
mapping:
total_fields:
limit: 5000
number_of_replicas: 0
sort:
field: '@timestamp'
order: desc
policy:
_meta:
managed: true
managed_by: security_onion
package:
name: elastic_agent
phases:
cold:
actions:
set_priority:
priority: 0
min_age: 60d
delete:
actions:
delete: {}
min_age: 365d
hot:
actions:
rollover:
max_age: 30d
max_primary_shard_size: 50gb
set_priority:
priority: 100
min_age: 0ms
warm:
actions:
set_priority:
priority: 50
min_age: 30d
so-logs-endpoint_x_actions:
index_sorting: false
index_template:

View File

@@ -23,7 +23,6 @@
{ "set": { "if": "ctx.event?.module == 'fim'", "override": true, "field": "event.module", "value": "file_integrity" } },
{ "rename": { "if": "ctx.winlog?.provider_name == 'Microsoft-Windows-Windows Defender'", "ignore_missing": true, "field": "winlog.event_data.Threat Name", "target_field": "winlog.event_data.threat_name" } },
{ "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.event?.dataset != null && ctx.event?.dataset == 'elasticsearch.server'", "field": "event.module", "value":"elasticsearch" }},
{"append": {"field":"related.ip","value":["{{source.ip}}","{{destination.ip}}"],"allow_duplicates":false,"if":"ctx?.event?.dataset == 'endpoint.events.network' && ctx?.source?.ip != null","ignore_failure":true}},
{"foreach": {"field":"host.ip","processor":{"append":{"field":"related.ip","value":"{{_ingest._value}}","allow_duplicates":false}},"if":"ctx?.event?.module == 'endpoint' && ctx?.host?.ip != null","ignore_missing":true, "description":"Extract IPs from Elastic Agent events (host.ip) and adds them to related.ip"}},
{ "remove": { "field": [ "message2", "type", "fields", "category", "module", "dataset", "event.dataset_temp", "dataset_tag_temp", "module_temp", "datastream_dataset_temp" ], "ignore_missing": true, "ignore_failure": true } }

View File

@@ -1,79 +1,15 @@
{
"description": "suricata.alert",
"processors": [
{
"set": {
"if": "ctx.event?.imported != true",
"field": "_index",
"value": "logs-suricata.alerts-so"
}
},
{
"set": {
"field": "tags",
"value": "alert"
}
},
{
"rename": {
"field": "message2.alert",
"target_field": "rule",
"ignore_missing": true,
"ignore_failure": true
}
},
{
"rename": {
"field": "rule.signature",
"target_field": "rule.name",
"ignore_missing": true,
"ignore_failure": true
}
},
{
"rename": {
"field": "rule.ref",
"target_field": "rule.version",
"ignore_missing": true,
"ignore_failure": true
}
},
{
"rename": {
"field": "rule.signature_id",
"target_field": "rule.uuid",
"ignore_missing": true,
"ignore_failure": true
}
},
{
"rename": {
"field": "rule.signature_id",
"target_field": "rule.signature",
"ignore_missing": true,
"ignore_failure": true
}
},
{
"rename": {
"field": "message2.payload_printable",
"target_field": "network.data.decoded",
"ignore_missing": true,
"ignore_failure": true
}
},
{
"dissect": {
"field": "rule.rule",
"pattern": "%{?prefix}content:\"%{dns.query_name}\"%{?remainder}",
"ignore_missing": true,
"ignore_failure": true
}
},
{
"pipeline": {
"name": "common.nids"
}
}
"description" : "suricata.alert",
"processors" : [
{ "set": { "if": "ctx.event?.imported != true", "field": "_index", "value": "logs-suricata.alerts-so" } },
{ "set": { "field": "tags","value": "alert" }},
{ "rename":{ "field": "message2.alert", "target_field": "rule", "ignore_failure": true } },
{ "rename":{ "field": "rule.signature", "target_field": "rule.name", "ignore_failure": true } },
{ "rename":{ "field": "rule.ref", "target_field": "rule.version", "ignore_failure": true } },
{ "rename":{ "field": "rule.signature_id", "target_field": "rule.uuid", "ignore_failure": true } },
{ "rename":{ "field": "rule.signature_id", "target_field": "rule.signature", "ignore_failure": true } },
{ "rename":{ "field": "message2.payload_printable", "target_field": "network.data.decoded", "ignore_failure": true } },
{ "dissect": { "field": "rule.rule", "pattern": "%{?prefix}content:\"%{dns.query_name}\"%{?remainder}", "ignore_missing": true, "ignore_failure": true } },
{ "pipeline": { "name": "common.nids" } }
]
}

View File

@@ -1,155 +1,30 @@
{
"description": "suricata.common",
"processors": [
{
"json": {
"field": "message",
"target_field": "message2",
"ignore_failure": true
}
},
{
"rename": {
"field": "message2.pkt_src",
"target_field": "network.packet_source",
"ignore_failure": true
}
},
{
"rename": {
"field": "message2.proto",
"target_field": "network.transport",
"ignore_failure": true
}
},
{
"rename": {
"field": "message2.in_iface",
"target_field": "observer.ingress.interface.name",
"ignore_failure": true
}
},
{
"rename": {
"field": "message2.flow_id",
"target_field": "log.id.uid",
"ignore_failure": true
}
},
{
"rename": {
"field": "message2.src_ip",
"target_field": "source.ip",
"ignore_failure": true
}
},
{
"rename": {
"field": "message2.src_port",
"target_field": "source.port",
"ignore_failure": true
}
},
{
"rename": {
"field": "message2.dest_ip",
"target_field": "destination.ip",
"ignore_failure": true
}
},
{
"rename": {
"field": "message2.dest_port",
"target_field": "destination.port",
"ignore_failure": true
}
},
{
"rename": {
"field": "message2.vlan",
"target_field": "network.vlan.id",
"ignore_failure": true
}
},
{
"rename": {
"field": "message2.community_id",
"target_field": "network.community_id",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.xff",
"target_field": "xff.ip",
"ignore_missing": true
}
},
{
"set": {
"field": "event.dataset",
"value": "{{ message2.event_type }}"
}
},
{
"set": {
"field": "observer.name",
"value": "{{agent.name}}"
}
},
{
"set": {
"field": "event.ingested",
"value": "{{@timestamp}}"
}
},
{
"date": {
"field": "message2.timestamp",
"target_field": "@timestamp",
"formats": [
"ISO8601",
"UNIX"
],
"timezone": "UTC",
"ignore_failure": true
}
},
{
"remove": {
"field": "agent",
"ignore_failure": true
}
},
{
"append": {
"field": "related.ip",
"value": [
"{{source.ip}}",
"{{destination.ip}}"
],
"allow_duplicates": false,
"ignore_failure": true
}
},
"description" : "suricata.common",
"processors" : [
{ "json": { "field": "message", "target_field": "message2", "ignore_failure": true } },
{ "rename": { "field": "message2.pkt_src", "target_field": "network.packet_source","ignore_failure": true } },
{ "rename": { "field": "message2.proto", "target_field": "network.transport", "ignore_failure": true } },
{ "rename": { "field": "message2.in_iface", "target_field": "observer.ingress.interface.name", "ignore_failure": true } },
{ "rename": { "field": "message2.flow_id", "target_field": "log.id.uid", "ignore_failure": true } },
{ "rename": { "field": "message2.src_ip", "target_field": "source.ip", "ignore_failure": true } },
{ "rename": { "field": "message2.src_port", "target_field": "source.port", "ignore_failure": true } },
{ "rename": { "field": "message2.dest_ip", "target_field": "destination.ip", "ignore_failure": true } },
{ "rename": { "field": "message2.dest_port", "target_field": "destination.port", "ignore_failure": true } },
{ "rename": { "field": "message2.vlan", "target_field": "network.vlan.id", "ignore_failure": true } },
{ "rename": { "field": "message2.community_id", "target_field": "network.community_id", "ignore_missing": true } },
{ "rename": { "field": "message2.xff", "target_field": "xff.ip", "ignore_missing": true } },
{ "set": { "field": "event.dataset", "value": "{{ message2.event_type }}" } },
{ "set": { "field": "observer.name", "value": "{{agent.name}}" } },
{ "set": { "field": "event.ingested", "value": "{{@timestamp}}" } },
{ "date": { "field": "message2.timestamp", "target_field": "@timestamp", "formats": ["ISO8601", "UNIX"], "timezone": "UTC", "ignore_failure": true } },
{ "remove":{ "field": "agent", "ignore_failure": true } },
{"append":{"field":"related.ip","value":["{{source.ip}}","{{destination.ip}}"],"allow_duplicates":false,"ignore_failure":true}},
{
"script": {
"source": "boolean isPrivate(def ip) { if (ip == null) return false; int dot1 = ip.indexOf('.'); if (dot1 == -1) return false; int dot2 = ip.indexOf('.', dot1 + 1); if (dot2 == -1) return false; int first = Integer.parseInt(ip.substring(0, dot1)); if (first == 10) return true; if (first == 192 && ip.startsWith('168.', dot1 + 1)) return true; if (first == 172) { int second = Integer.parseInt(ip.substring(dot1 + 1, dot2)); return second >= 16 && second <= 31; } return false; } String[] fields = new String[] {\"source\", \"destination\"}; for (int i = 0; i < fields.length; i++) { def field = fields[i]; def ip = ctx[field]?.ip; if (ip != null) { if (ctx.network == null) ctx.network = new HashMap(); if (isPrivate(ip)) { if (ctx.network.private_ip == null) ctx.network.private_ip = new ArrayList(); if (!ctx.network.private_ip.contains(ip)) ctx.network.private_ip.add(ip); } else { if (ctx.network.public_ip == null) ctx.network.public_ip = new ArrayList(); if (!ctx.network.public_ip.contains(ip)) ctx.network.public_ip.add(ip); } } }",
"ignore_failure": false
}
},
{
"rename": {
"field": "message2.capture_file",
"target_field": "suricata.capture_file",
"ignore_missing": true
}
},
{
"pipeline": {
"if": "ctx?.event?.dataset != null",
"name": "suricata.{{event.dataset}}"
}
}
{ "pipeline": { "if": "ctx?.event?.dataset != null", "name": "suricata.{{event.dataset}}" } }
]
}

View File

@@ -1,136 +1,21 @@
{
"description": "suricata.dns",
"processors": [
{
"rename": {
"field": "message2.proto",
"target_field": "network.transport",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.app_proto",
"target_field": "network.protocol",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.dns.type",
"target_field": "dns.query.type",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.dns.tx_id",
"target_field": "dns.tx_id",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.dns.id",
"target_field": "dns.id",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.dns.version",
"target_field": "dns.version",
"ignore_missing": true
}
},
{
"pipeline": {
"name": "suricata.dnsv3",
"ignore_missing_pipeline": true,
"if": "ctx?.dns?.version != null && ctx?.dns?.version == 3",
"ignore_failure": true
}
},
{
"rename": {
"field": "message2.dns.rrname",
"target_field": "dns.query.name",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.dns.rrtype",
"target_field": "dns.query.type_name",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.dns.flags",
"target_field": "dns.flags",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.dns.qr",
"target_field": "dns.qr",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.dns.rd",
"target_field": "dns.recursion.desired",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.dns.ra",
"target_field": "dns.recursion.available",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.dns.opcode",
"target_field": "dns.opcode",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.dns.rcode",
"target_field": "dns.response.code_name",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.dns.grouped.A",
"target_field": "dns.answers.data",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.dns.grouped.CNAME",
"target_field": "dns.answers.name",
"ignore_missing": true
}
},
{
"pipeline": {
"if": "ctx.dns.query?.name != null && ctx.dns.query.name.contains('.')",
"name": "dns.tld"
}
},
{
"pipeline": {
"name": "common"
}
}
"description" : "suricata.dns",
"processors" : [
{ "rename": { "field": "message2.proto", "target_field": "network.transport", "ignore_missing": true } },
{ "rename": { "field": "message2.app_proto", "target_field": "network.protocol", "ignore_missing": true } },
{ "rename": { "field": "message2.dns.type", "target_field": "dns.query.type", "ignore_missing": true } },
{ "rename": { "field": "message2.dns.tx_id", "target_field": "dns.id", "ignore_missing": true } },
{ "rename": { "field": "message2.dns.version", "target_field": "dns.version", "ignore_missing": true } },
{ "rename": { "field": "message2.dns.rrname", "target_field": "dns.query.name", "ignore_missing": true } },
{ "rename": { "field": "message2.dns.rrtype", "target_field": "dns.query.type_name", "ignore_missing": true } },
{ "rename": { "field": "message2.dns.flags", "target_field": "dns.flags", "ignore_missing": true } },
{ "rename": { "field": "message2.dns.qr", "target_field": "dns.qr", "ignore_missing": true } },
{ "rename": { "field": "message2.dns.rd", "target_field": "dns.recursion.desired", "ignore_missing": true } },
{ "rename": { "field": "message2.dns.ra", "target_field": "dns.recursion.available", "ignore_missing": true } },
{ "rename": { "field": "message2.dns.rcode", "target_field": "dns.response.code_name", "ignore_missing": true } },
{ "rename": { "field": "message2.dns.grouped.A", "target_field": "dns.answers.data", "ignore_missing": true } },
{ "rename": { "field": "message2.dns.grouped.CNAME", "target_field": "dns.answers.name", "ignore_missing": true } },
{ "pipeline": { "if": "ctx.dns.query?.name != null && ctx.dns.query.name.contains('.')", "name": "dns.tld" } },
{ "pipeline": { "name": "common" } }
]
}

View File

@@ -1,56 +0,0 @@
{
"processors": [
{
"rename": {
"field": "message2.dns.queries",
"target_field": "dns.queries",
"ignore_missing": true,
"ignore_failure": true
}
},
{
"script": {
"source": "if (ctx?.dns?.queries != null && ctx?.dns?.queries.length > 0) {\n if (ctx.dns == null) {\n ctx.dns = new HashMap();\n }\n if (ctx.dns.query == null) {\n ctx.dns.query = new HashMap();\n }\n ctx.dns.query.name = ctx?.dns?.queries[0].rrname;\n}"
}
},
{
"script": {
"source": "if (ctx?.dns?.queries != null && ctx?.dns?.queries.length > 0) {\n if (ctx.dns == null) {\n ctx.dns = new HashMap();\n }\n if (ctx.dns.query == null) {\n ctx.dns.query = new HashMap();\n }\n ctx.dns.query.type_name = ctx?.dns?.queries[0].rrtype;\n}"
}
},
{
"foreach": {
"field": "dns.queries",
"processor": {
"rename": {
"field": "_ingest._value.rrname",
"target_field": "_ingest._value.name",
"ignore_missing": true
}
},
"ignore_failure": true
}
},
{
"foreach": {
"field": "dns.queries",
"processor": {
"rename": {
"field": "_ingest._value.rrtype",
"target_field": "_ingest._value.type_name",
"ignore_missing": true
}
},
"ignore_failure": true
}
},
{
"pipeline": {
"name": "suricata.tld",
"ignore_missing_pipeline": true,
"if": "ctx?.dns?.queries != null && ctx?.dns?.queries.length > 0",
"ignore_failure": true
}
}
]
}

View File

@@ -1,52 +0,0 @@
{
"processors": [
{
"script": {
"source": "if (ctx.dns != null && ctx.dns.queries != null) {\n for (def q : ctx.dns.queries) {\n if (q.name != null && q.name.contains('.')) {\n q.top_level_domain = q.name.substring(q.name.lastIndexOf('.') + 1);\n }\n }\n}",
"ignore_failure": true
}
},
{
"script": {
"source": "if (ctx.dns != null && ctx.dns.queries != null) {\n for (def q : ctx.dns.queries) {\n if (q.name != null && q.name.contains('.')) {\n q.query_without_tld = q.name.substring(0, q.name.lastIndexOf('.'));\n }\n }\n}",
"ignore_failure": true
}
},
{
"script": {
"source": "if (ctx.dns != null && ctx.dns.queries != null) {\n for (def q : ctx.dns.queries) {\n if (q.query_without_tld != null && q.query_without_tld.contains('.')) {\n q.parent_domain = q.query_without_tld.substring(q.query_without_tld.lastIndexOf('.') + 1);\n }\n }\n}",
"ignore_failure": true
}
},
{
"script": {
"source": "if (ctx.dns != null && ctx.dns.queries != null) {\n for (def q : ctx.dns.queries) {\n if (q.query_without_tld != null && q.query_without_tld.contains('.')) {\n q.subdomain = q.query_without_tld.substring(0, q.query_without_tld.lastIndexOf('.'));\n }\n }\n}",
"ignore_failure": true
}
},
{
"script": {
"source": "if (ctx.dns != null && ctx.dns.queries != null) {\n for (def q : ctx.dns.queries) {\n if (q.parent_domain != null && q.top_level_domain != null) {\n q.highest_registered_domain = q.parent_domain + \".\" + q.top_level_domain;\n }\n }\n}",
"ignore_failure": true
}
},
{
"script": {
"source": "if (ctx.dns != null && ctx.dns.queries != null) {\n for (def q : ctx.dns.queries) {\n if (q.subdomain != null) {\n q.subdomain_length = q.subdomain.length();\n }\n }\n}",
"ignore_failure": true
}
},
{
"script": {
"source": "if (ctx.dns != null && ctx.dns.queries != null) {\n for (def q : ctx.dns.queries) {\n if (q.parent_domain != null) {\n q.parent_domain_length = q.parent_domain.length();\n }\n }\n}",
"ignore_failure": true
}
},
{
"script": {
"source": "if (ctx.dns != null && ctx.dns.queries != null) {\n for (def q : ctx.dns.queries) {\n q.remove('query_without_tld');\n }\n}",
"ignore_failure": true
}
}
]
}

View File

@@ -1,61 +0,0 @@
{
"description": "zeek.analyzer",
"processors": [
{
"set": {
"field": "event.dataset",
"value": "analyzer"
}
},
{
"remove": {
"field": [
"host"
],
"ignore_failure": true
}
},
{
"json": {
"field": "message",
"target_field": "message2",
"ignore_failure": true
}
},
{
"set": {
"field": "network.protocol",
"copy_from": "message2.analyzer_name",
"ignore_empty_value": true,
"if": "ctx?.message2?.analyzer_kind == 'protocol'"
}
},
{
"set": {
"field": "network.protocol",
"ignore_empty_value": true,
"if": "ctx?.message2?.analyzer_kind != 'protocol'",
"copy_from": "message2.proto"
}
},
{
"lowercase": {
"field": "network.protocol",
"ignore_missing": true,
"ignore_failure": true
}
},
{
"rename": {
"field": "message2.failure_reason",
"target_field": "error.reason",
"ignore_missing": true
}
},
{
"pipeline": {
"name": "zeek.common"
}
}
]
}

View File

@@ -1,227 +1,35 @@
{
"description": "zeek.dns",
"processors": [
{
"set": {
"field": "event.dataset",
"value": "dns"
}
},
{
"remove": {
"field": [
"host"
],
"ignore_failure": true
}
},
{
"json": {
"field": "message",
"target_field": "message2",
"ignore_failure": true
}
},
{
"dot_expander": {
"field": "id.orig_h",
"path": "message2",
"ignore_failure": true
}
},
{
"rename": {
"field": "message2.proto",
"target_field": "network.transport",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.trans_id",
"target_field": "dns.id",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.rtt",
"target_field": "event.duration",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.query",
"target_field": "dns.query.name",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.qclass",
"target_field": "dns.query.class",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.qclass_name",
"target_field": "dns.query.class_name",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.qtype",
"target_field": "dns.query.type",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.qtype_name",
"target_field": "dns.query.type_name",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.rcode",
"target_field": "dns.response.code",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.rcode_name",
"target_field": "dns.response.code_name",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.AA",
"target_field": "dns.authoritative",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.TC",
"target_field": "dns.truncated",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.RD",
"target_field": "dns.recursion.desired",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.RA",
"target_field": "dns.recursion.available",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.Z",
"target_field": "dns.reserved",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.answers",
"target_field": "dns.answers.name",
"ignore_missing": true
}
},
{
"foreach": {
"field": "dns.answers.name",
"processor": {
"pipeline": {
"name": "common.ip_validation"
}
},
"if": "ctx.dns != null && ctx.dns.answers != null && ctx.dns.answers.name != null",
"ignore_failure": true
}
},
{
"foreach": {
"field": "temp._valid_ips",
"processor": {
"append": {
"field": "dns.resolved_ip",
"allow_duplicates": false,
"value": "{{{_ingest._value}}}",
"ignore_failure": true
}
},
"if": "ctx.dns != null && ctx.dns.answers != null && ctx.dns.answers.name != null",
"ignore_failure": true
}
},
{
"script": {
"source": "if (ctx.dns.resolved_ip != null && ctx.dns.resolved_ip instanceof List) {\n ctx.dns.resolved_ip.removeIf(item -> item == null || item.toString().trim().isEmpty());\n }",
"ignore_failure": true
}
},
{
"remove": {
"field": [
"temp"
],
"ignore_missing": true,
"ignore_failure": true
}
},
{
"rename": {
"field": "message2.TTLs",
"target_field": "dns.ttls",
"ignore_missing": true
}
},
{
"rename": {
"field": "message2.rejected",
"target_field": "dns.query.rejected",
"ignore_missing": true
}
},
{
"script": {
"lang": "painless",
"source": "ctx.dns.query.length = ctx.dns.query.name.length()",
"ignore_failure": true
}
},
{
"set": {
"if": "ctx._index == 'so-zeek'",
"field": "_index",
"value": "so-zeek_dns",
"override": true
}
},
{
"pipeline": {
"if": "ctx.dns?.query?.name != null && ctx.dns.query.name.contains('.')",
"name": "dns.tld"
}
},
{
"pipeline": {
"name": "zeek.common"
}
}
"description" : "zeek.dns",
"processors" : [
{ "set": { "field": "event.dataset", "value": "dns" } },
{ "remove": { "field": ["host"], "ignore_failure": true } },
{ "json": { "field": "message", "target_field": "message2", "ignore_failure": true } },
{ "dot_expander": { "field": "id.orig_h", "path": "message2", "ignore_failure": true } },
{ "rename": { "field": "message2.proto", "target_field": "network.transport", "ignore_missing": true } },
{ "rename": { "field": "message2.trans_id", "target_field": "dns.id", "ignore_missing": true } },
{ "rename": { "field": "message2.rtt", "target_field": "event.duration", "ignore_missing": true } },
{ "rename": { "field": "message2.query", "target_field": "dns.query.name", "ignore_missing": true } },
{ "rename": { "field": "message2.qclass", "target_field": "dns.query.class", "ignore_missing": true } },
{ "rename": { "field": "message2.qclass_name", "target_field": "dns.query.class_name", "ignore_missing": true } },
{ "rename": { "field": "message2.qtype", "target_field": "dns.query.type", "ignore_missing": true } },
{ "rename": { "field": "message2.qtype_name", "target_field": "dns.query.type_name", "ignore_missing": true } },
{ "rename": { "field": "message2.rcode", "target_field": "dns.response.code", "ignore_missing": true } },
{ "rename": { "field": "message2.rcode_name", "target_field": "dns.response.code_name", "ignore_missing": true } },
{ "rename": { "field": "message2.AA", "target_field": "dns.authoritative", "ignore_missing": true } },
{ "rename": { "field": "message2.TC", "target_field": "dns.truncated", "ignore_missing": true } },
{ "rename": { "field": "message2.RD", "target_field": "dns.recursion.desired", "ignore_missing": true } },
{ "rename": { "field": "message2.RA", "target_field": "dns.recursion.available", "ignore_missing": true } },
{ "rename": { "field": "message2.Z", "target_field": "dns.reserved", "ignore_missing": true } },
{ "rename": { "field": "message2.answers", "target_field": "dns.answers.name", "ignore_missing": true } },
{ "foreach": {"field": "dns.answers.name","processor": {"pipeline": {"name": "common.ip_validation"}},"if": "ctx.dns != null && ctx.dns.answers != null && ctx.dns.answers.name != null","ignore_failure": true}},
{ "foreach": {"field": "temp._valid_ips","processor": {"append": {"field": "dns.resolved_ip","allow_duplicates": false,"value": "{{{_ingest._value}}}","ignore_failure": true}},"ignore_failure": true}},
{ "script": { "source": "if (ctx.dns.resolved_ip != null && ctx.dns.resolved_ip instanceof List) {\n ctx.dns.resolved_ip.removeIf(item -> item == null || item.toString().trim().isEmpty());\n }","ignore_failure": true }},
{ "remove": {"field": ["temp"], "ignore_missing": true ,"ignore_failure": true } },
{ "rename": { "field": "message2.TTLs", "target_field": "dns.ttls", "ignore_missing": true } },
{ "rename": { "field": "message2.rejected", "target_field": "dns.query.rejected", "ignore_missing": true } },
{ "script": { "lang": "painless", "source": "ctx.dns.query.length = ctx.dns.query.name.length()", "ignore_failure": true } },
{ "set": { "if": "ctx._index == 'so-zeek'", "field": "_index", "value": "so-zeek_dns", "override": true } },
{ "pipeline": { "if": "ctx.dns?.query?.name != null && ctx.dns.query.name.contains('.')", "name": "dns.tld" } },
{ "pipeline": { "name": "zeek.common" } }
]
}

View File

@@ -0,0 +1,20 @@
{
"description" : "zeek.dpd",
"processors" : [
{ "set": { "field": "event.dataset", "value": "dpd" } },
{ "remove": { "field": ["host"], "ignore_failure": true } },
{ "json": { "field": "message", "target_field": "message2", "ignore_failure": true } },
{ "dot_expander": { "field": "id.orig_h", "path": "message2", "ignore_failure": true } },
{ "rename": { "field": "message2.id.orig_h", "target_field": "source.ip", "ignore_missing": true } },
{ "dot_expander": { "field": "id.orig_p", "path": "message2", "ignore_failure": true } },
{ "rename": { "field": "message2.id.orig_p", "target_field": "source.port", "ignore_missing": true } },
{ "dot_expander": { "field": "id.resp_h", "path": "message2", "ignore_failure": true } },
{ "rename": { "field": "message2.id.resp_h", "target_field": "destination.ip", "ignore_missing": true } },
{ "dot_expander": { "field": "id.resp_p", "path": "message2", "ignore_failure": true } },
{ "rename": { "field": "message2.id.resp_p", "target_field": "destination.port", "ignore_missing": true } },
{ "rename": { "field": "message2.proto", "target_field": "network.protocol", "ignore_missing": true } },
{ "rename": { "field": "message2.analyzer", "target_field": "observer.analyzer", "ignore_missing": true } },
{ "rename": { "field": "message2.failure_reason", "target_field": "error.reason", "ignore_missing": true } },
{ "pipeline": { "name": "zeek.common" } }
]
}

View File

@@ -20,28 +20,8 @@ appender.rolling.strategy.type = DefaultRolloverStrategy
appender.rolling.strategy.action.type = Delete
appender.rolling.strategy.action.basepath = /var/log/elasticsearch
appender.rolling.strategy.action.condition.type = IfFileName
appender.rolling.strategy.action.condition.glob = *.log.gz
appender.rolling.strategy.action.condition.glob = *.gz
appender.rolling.strategy.action.condition.nested_condition.type = IfLastModified
appender.rolling.strategy.action.condition.nested_condition.age = 7D
appender.rolling_json.type = RollingFile
appender.rolling_json.name = rolling_json
appender.rolling_json.fileName = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}.json
appender.rolling_json.layout.type = ECSJsonLayout
appender.rolling_json.layout.dataset = elasticsearch.server
appender.rolling_json.filePattern = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}-%d{yyyy-MM-dd}.json.gz
appender.rolling_json.policies.type = Policies
appender.rolling_json.policies.time.type = TimeBasedTriggeringPolicy
appender.rolling_json.policies.time.interval = 1
appender.rolling_json.policies.time.modulate = true
appender.rolling_json.strategy.type = DefaultRolloverStrategy
appender.rolling_json.strategy.action.type = Delete
appender.rolling_json.strategy.action.basepath = /var/log/elasticsearch
appender.rolling_json.strategy.action.condition.type = IfFileName
appender.rolling_json.strategy.action.condition.glob = *.json.gz
appender.rolling_json.strategy.action.condition.nested_condition.type = IfLastModified
appender.rolling_json.strategy.action.condition.nested_condition.age = 1D
rootLogger.level = info
rootLogger.appenderRef.rolling.ref = rolling
rootLogger.appenderRef.rolling_json.ref = rolling_json

View File

@@ -392,7 +392,6 @@ elasticsearch:
so-logs-elastic_agent_x_metricbeat: *indexSettings
so-logs-elastic_agent_x_osquerybeat: *indexSettings
so-logs-elastic_agent_x_packetbeat: *indexSettings
so-logs-elasticsearch_x_server: *indexSettings
so-metrics-endpoint_x_metadata: *indexSettings
so-metrics-endpoint_x_metrics: *indexSettings
so-metrics-endpoint_x_policy: *indexSettings

View File

@@ -841,10 +841,6 @@
"type": "long"
}
}
},
"capture_file": {
"type": "keyword",
"ignore_above": 1024
}
}
}

View File

@@ -13,7 +13,6 @@
{# Import defaults.yaml for model hardware capabilities #}
{% import_yaml 'hypervisor/defaults.yaml' as DEFAULTS %}
{% set HYPERVISORMERGED = salt['pillar.get']('hypervisor', default=DEFAULTS.hypervisor, merge=True) %}
{# Get hypervisor nodes from pillar #}
{% set NODES = salt['pillar.get']('hypervisor:nodes', {}) %}
@@ -31,10 +30,9 @@
{% set model = '' %}
{% if grains %}
{% set minion_id = grains.keys() | first %}
{% set model = grains[minion_id].get('sosmodel', grains[minion_id].get('byodmodel', '')) %}
{% set model = grains[minion_id].get('sosmodel', '') %}
{% endif %}
{% set model_config = HYPERVISORMERGED.model.get(model, {}) %}
{% set model_config = DEFAULTS.hypervisor.model.get(model, {}) %}
{# Get VM list from VMs file #}
{% set vms = {} %}
@@ -58,26 +56,10 @@
{% set role = vm.get('role', '') %}
{% do salt.log.debug('salt/hypervisor/map.jinja: Processing VM - hostname: ' ~ hostname ~ ', role: ' ~ role) %}
{# Try to load VM configuration from config file first, then .error file if config doesn't exist #}
{# Load VM configuration from config file #}
{% set vm_file = 'hypervisor/hosts/' ~ hypervisor ~ '/' ~ hostname ~ '_' ~ role %}
{% set vm_error_file = vm_file ~ '.error' %}
{% do salt.log.debug('salt/hypervisor/map.jinja: VM config file: ' ~ vm_file) %}
{# Check if base config file exists #}
{% set config_exists = salt['file.file_exists']('/opt/so/saltstack/local/salt/' ~ vm_file) %}
{% set error_exists = salt['file.file_exists']('/opt/so/saltstack/local/salt/' ~ vm_error_file) %}
{% set vm_state = none %}
{% if config_exists %}
{% import_json vm_file as vm_state %}
{% do salt.log.debug('salt/hypervisor/map.jinja: Loaded VM config from base file') %}
{% elif error_exists %}
{% import_json vm_error_file as vm_state %}
{% do salt.log.debug('salt/hypervisor/map.jinja: Loaded VM config from .error file') %}
{% else %}
{% do salt.log.warning('salt/hypervisor/map.jinja: No config or error file found for VM ' ~ hostname ~ '_' ~ role) %}
{% endif %}
{% if vm_state %}
{% do salt.log.debug('salt/hypervisor/map.jinja: VM config content: ' ~ vm_state | tojson) %}
{% set vm_data = {'config': vm_state.config} %}
@@ -101,7 +83,7 @@
{% endif %}
{% do vms.update({hostname ~ '_' ~ role: vm_data}) %}
{% else %}
{% do salt.log.debug('salt/hypervisor/map.jinja: Skipping VM ' ~ hostname ~ '_' ~ role ~ ' - no config available') %}
{% do salt.log.debug('salt/hypervisor/map.jinja: Config file empty: ' ~ vm_file) %}
{% endif %}
{% endfor %}

View File

@@ -30,9 +30,7 @@
#
# WARNING: This script will DESTROY all data on the target drives!
#
# USAGE:
# sudo ./so-nvme-raid1.sh # Normal operation
# sudo ./so-nvme-raid1.sh --force-cleanup # Force cleanup of existing RAID
# USAGE: sudo ./so-nvme-raid1.sh
#
#################################################################
@@ -43,19 +41,6 @@ set -e
RAID_ARRAY_NAME="md0"
RAID_DEVICE="/dev/${RAID_ARRAY_NAME}"
MOUNT_POINT="/nsm"
FORCE_CLEANUP=false
# Parse command line arguments
for arg in "$@"; do
case $arg in
--force-cleanup)
FORCE_CLEANUP=true
shift
;;
*)
;;
esac
done
# Function to log messages
log() {
@@ -70,91 +55,6 @@ check_root() {
fi
}
# Function to force cleanup all RAID components
force_cleanup_raid() {
log "=== FORCE CLEANUP MODE ==="
log "This will destroy all RAID configurations and data on target drives!"
# Stop all MD arrays
log "Stopping all MD arrays"
mdadm --stop --scan 2>/dev/null || true
# Wait for arrays to stop
sleep 2
# Remove any running md devices
for md in /dev/md*; do
if [ -b "$md" ]; then
log "Stopping $md"
mdadm --stop "$md" 2>/dev/null || true
fi
done
# Force cleanup both NVMe drives
for device in "/dev/nvme0n1" "/dev/nvme1n1"; do
log "Force cleaning $device"
# Kill any processes using the device
fuser -k "${device}"* 2>/dev/null || true
# Unmount any mounted partitions
for part in "${device}"*; do
if [ -b "$part" ]; then
umount -f "$part" 2>/dev/null || true
fi
done
# Force zero RAID superblocks on partitions
for part in "${device}"p*; do
if [ -b "$part" ]; then
log "Zeroing RAID superblock on $part"
mdadm --zero-superblock --force "$part" 2>/dev/null || true
fi
done
# Zero superblock on the device itself
log "Zeroing RAID superblock on $device"
mdadm --zero-superblock --force "$device" 2>/dev/null || true
# Remove LVM physical volumes
pvremove -ff -y "$device" 2>/dev/null || true
# Wipe all filesystem and partition signatures
log "Wiping all signatures from $device"
wipefs -af "$device" 2>/dev/null || true
# Overwrite the beginning of the drive (partition table area)
log "Clearing partition table on $device"
dd if=/dev/zero of="$device" bs=1M count=10 2>/dev/null || true
# Clear the end of the drive (backup partition table area)
local device_size=$(blockdev --getsz "$device" 2>/dev/null || echo "0")
if [ "$device_size" -gt 0 ]; then
dd if=/dev/zero of="$device" bs=512 seek=$(( device_size - 2048 )) count=2048 2>/dev/null || true
fi
# Force kernel to re-read partition table
blockdev --rereadpt "$device" 2>/dev/null || true
partprobe -s "$device" 2>/dev/null || true
done
# Clear mdadm configuration
log "Clearing mdadm configuration"
echo "DEVICE partitions" > /etc/mdadm.conf
# Remove any fstab entries for the RAID device or mount point
log "Cleaning fstab entries"
sed -i "\|${RAID_DEVICE}|d" /etc/fstab
sed -i "\|${MOUNT_POINT}|d" /etc/fstab
# Wait for system to settle
udevadm settle
sleep 5
log "Force cleanup complete!"
log "Proceeding with RAID setup..."
}
# Function to find MD arrays using specific devices
find_md_arrays_using_devices() {
local target_devices=("$@")
@@ -305,15 +205,10 @@ check_existing_raid() {
fi
log "Error: $device appears to be part of an existing RAID array"
log "Old RAID metadata detected but array is not running."
log ""
log "To fix this, run the script with --force-cleanup:"
log " sudo $0 --force-cleanup"
log ""
log "Or manually clean up with:"
log "1. Stop any arrays: mdadm --stop --scan"
log "2. Zero superblocks: mdadm --zero-superblock --force ${device}p1"
log "3. Wipe signatures: wipefs -af $device"
log "To reuse this device, you must first:"
log "1. Unmount any filesystems"
log "2. Stop the RAID array: mdadm --stop $array_name"
log "3. Zero the superblock: mdadm --zero-superblock ${device}p1"
exit 1
fi
done
@@ -343,7 +238,7 @@ ensure_devices_free() {
done
# Clear MD superblock
mdadm --zero-superblock --force "${device}"* 2>/dev/null || true
mdadm --zero-superblock "${device}"* 2>/dev/null || true
# Remove LVM PV if exists
pvremove -ff -y "$device" 2>/dev/null || true
@@ -368,11 +263,6 @@ main() {
# Check if running as root
check_root
# If force cleanup flag is set, do aggressive cleanup first
if [ "$FORCE_CLEANUP" = true ]; then
force_cleanup_raid
fi
# Check for existing RAID setup
check_existing_raid

View File

@@ -1,591 +0,0 @@
#!/usr/bin/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.
#
# Note: Per the Elastic License 2.0, the second limitation states:
#
# "You may not move, change, disable, or circumvent the license key functionality
# in the software, and you may not remove or obscure any functionality in the
# software that is protected by the license key."
{% if 'vrt' in salt['pillar.get']('features', []) %}
"""
Script for creating and attaching virtual volumes to KVM virtual machines for NSM storage.
This script provides functionality to create pre-allocated raw disk images and attach them
to VMs as virtio-blk devices for high-performance network security monitoring data storage.
The script handles the complete volume lifecycle:
1. Volume Creation: Creates pre-allocated raw disk images using qemu-img
2. Volume Attachment: Attaches volumes to VMs as virtio-blk devices
3. VM Management: Stops/starts VMs as needed during the process
This script is designed to work with Security Onion's virtualization infrastructure and is typically
used during VM provisioning to add dedicated NSM storage volumes.
**Usage:**
so-kvm-create-volume -v <vm_name> -s <size_gb> [-S]
**Options:**
-v, --vm Name of the virtual machine to attach the volume to (required).
-s, --size Size of the volume in GB (required, must be a positive integer).
-S, --start Start the VM after volume creation and attachment (optional).
**Examples:**
1. **Create and Attach 500GB Volume:**
```bash
so-kvm-create-volume -v vm1_sensor -s 500
```
This command creates and attaches a volume with the following settings:
- VM Name: `vm1_sensor`
- Volume Size: `500` GB
- Volume Path: `/nsm/libvirt/volumes/vm1_sensor-nsm-<epoch_timestamp>.img`
- Device: `/dev/vdb` (virtio-blk)
- VM remains stopped after attachment
2. **Create Volume and Start VM:**
```bash
so-kvm-create-volume -v vm2_sensor -s 1000 -S
```
This command creates a volume and starts the VM:
- VM Name: `vm2_sensor`
- Volume Size: `1000` GB (1 TB)
- VM is started after volume attachment due to the `-S` flag
3. **Create Large Volume for Heavy Traffic:**
```bash
so-kvm-create-volume -v vm3_sensor -s 2000 -S
```
This command creates a large volume for high-traffic environments:
- VM Name: `vm3_sensor`
- Volume Size: `2000` GB (2 TB)
- VM is started after attachment
**Notes:**
- The script automatically stops the VM if it's running before creating and attaching the volume.
- Volumes are created with full pre-allocation for optimal performance.
- Volume files are stored in `/nsm/libvirt/volumes/` with naming pattern `<vm_name>-nsm-<epoch_timestamp>.img`.
- The epoch timestamp ensures unique volume names and prevents conflicts.
- Volumes are attached as `/dev/vdb` using virtio-blk for high performance.
- The script checks available disk space before creating the volume.
- Ownership is set to `qemu:qemu` with permissions `640`.
- Without the `-S` flag, the VM remains stopped after volume attachment.
**Description:**
The `so-kvm-create-volume` script creates and attaches NSM storage volumes using the following process:
1. **Pre-flight Checks:**
- Validates input parameters (VM name, size)
- Checks available disk space in `/nsm/libvirt/volumes/`
- Ensures sufficient space for the requested volume size
2. **VM State Management:**
- Connects to the local libvirt daemon
- Stops the VM if it's currently running
- Retrieves current VM configuration
3. **Volume Creation:**
- Creates volume directory if it doesn't exist
- Uses `qemu-img create` with full pre-allocation
- Sets proper ownership (qemu:qemu) and permissions (640)
- Validates volume creation success
4. **Volume Attachment:**
- Modifies VM's libvirt XML configuration
- Adds disk element with virtio-blk driver
- Configures cache='none' and io='native' for performance
- Attaches volume as `/dev/vdb`
5. **VM Redefinition:**
- Applies the new configuration by redefining the VM
- Optionally starts the VM if requested
- Emits deployment status events for monitoring
6. **Error Handling:**
- Validates all input parameters
- Checks disk space before creation
- Handles volume creation failures
- Handles volume attachment failures
- Provides detailed error messages for troubleshooting
**Exit Codes:**
- `0`: Success
- `1`: An error occurred during execution
**Logging:**
- Logs are written to `/opt/so/log/hypervisor/so-kvm-create-volume.log`
- Both file and console logging are enabled for real-time monitoring
- Log entries include timestamps and severity levels
- Log prefixes: VOLUME:, VM:, HARDWARE:, SPACE:
- Detailed error messages are logged for troubleshooting
"""
import argparse
import sys
import os
import libvirt
import logging
import socket
import subprocess
import pwd
import grp
import time
import xml.etree.ElementTree as ET
from io import StringIO
from so_vm_utils import start_vm, stop_vm
from so_logging_utils import setup_logging
# Get hypervisor name from local hostname
HYPERVISOR = socket.gethostname()
# Volume storage directory
VOLUME_DIR = '/nsm/libvirt/volumes'
# Custom exception classes
class InsufficientSpaceError(Exception):
"""Raised when there is insufficient disk space for volume creation."""
pass
class VolumeCreationError(Exception):
"""Raised when volume creation fails."""
pass
class VolumeAttachmentError(Exception):
"""Raised when volume attachment fails."""
pass
# Custom log handler to capture output
class StringIOHandler(logging.Handler):
def __init__(self):
super().__init__()
self.strio = StringIO()
def emit(self, record):
msg = self.format(record)
self.strio.write(msg + '\n')
def get_value(self):
return self.strio.getvalue()
def parse_arguments():
"""Parse command-line arguments."""
parser = argparse.ArgumentParser(description='Create and attach a virtual volume to a KVM virtual machine for NSM storage.')
parser.add_argument('-v', '--vm', required=True, help='Name of the virtual machine to attach the volume to.')
parser.add_argument('-s', '--size', type=int, required=True, help='Size of the volume in GB (must be a positive integer).')
parser.add_argument('-S', '--start', action='store_true', help='Start the VM after volume creation and attachment.')
args = parser.parse_args()
# Validate size is positive
if args.size <= 0:
parser.error("Volume size must be a positive integer.")
return args
def check_disk_space(size_gb, logger):
"""
Check if there is sufficient disk space available for volume creation.
Args:
size_gb: Size of the volume in GB
logger: Logger instance
Raises:
InsufficientSpaceError: If there is not enough disk space
"""
try:
stat = os.statvfs(VOLUME_DIR)
# Available space in bytes
available_bytes = stat.f_bavail * stat.f_frsize
# Required space in bytes (add 10% buffer)
required_bytes = size_gb * 1024 * 1024 * 1024 * 1.1
available_gb = available_bytes / (1024 * 1024 * 1024)
required_gb = required_bytes / (1024 * 1024 * 1024)
logger.info(f"SPACE: Available: {available_gb:.2f} GB, Required: {required_gb:.2f} GB")
if available_bytes < required_bytes:
raise InsufficientSpaceError(
f"Insufficient disk space. Available: {available_gb:.2f} GB, Required: {required_gb:.2f} GB"
)
logger.info(f"SPACE: Sufficient disk space available for {size_gb} GB volume")
except OSError as e:
logger.error(f"SPACE: Failed to check disk space: {e}")
raise
def create_volume_file(vm_name, size_gb, logger):
"""
Create a pre-allocated raw disk image for the VM.
Args:
vm_name: Name of the VM
size_gb: Size of the volume in GB
logger: Logger instance
Returns:
Path to the created volume file
Raises:
VolumeCreationError: If volume creation fails
"""
# Generate epoch timestamp for unique volume naming
epoch_timestamp = int(time.time())
# Define volume path with epoch timestamp for uniqueness
volume_path = os.path.join(VOLUME_DIR, f"{vm_name}-nsm-{epoch_timestamp}.img")
# Check if volume already exists (shouldn't be possible with timestamp)
if os.path.exists(volume_path):
logger.error(f"VOLUME: Volume already exists: {volume_path}")
raise VolumeCreationError(f"Volume already exists: {volume_path}")
logger.info(f"VOLUME: Creating {size_gb} GB volume at {volume_path}")
# Create volume using qemu-img with full pre-allocation
try:
cmd = [
'qemu-img', 'create',
'-f', 'raw',
'-o', 'preallocation=full',
volume_path,
f"{size_gb}G"
]
result = subprocess.run(
cmd,
capture_output=True,
text=True,
check=True
)
logger.info(f"VOLUME: Volume created successfully")
if result.stdout:
logger.debug(f"VOLUME: qemu-img output: {result.stdout.strip()}")
except subprocess.CalledProcessError as e:
logger.error(f"VOLUME: Failed to create volume: {e}")
if e.stderr:
logger.error(f"VOLUME: qemu-img error: {e.stderr.strip()}")
raise VolumeCreationError(f"Failed to create volume: {e}")
# Set ownership to qemu:qemu
try:
qemu_uid = pwd.getpwnam('qemu').pw_uid
qemu_gid = grp.getgrnam('qemu').gr_gid
os.chown(volume_path, qemu_uid, qemu_gid)
logger.info(f"VOLUME: Set ownership to qemu:qemu")
except (KeyError, OSError) as e:
logger.error(f"VOLUME: Failed to set ownership: {e}")
raise VolumeCreationError(f"Failed to set ownership: {e}")
# Set permissions to 640
try:
os.chmod(volume_path, 0o640)
logger.info(f"VOLUME: Set permissions to 640")
except OSError as e:
logger.error(f"VOLUME: Failed to set permissions: {e}")
raise VolumeCreationError(f"Failed to set permissions: {e}")
# Verify volume was created
if not os.path.exists(volume_path):
logger.error(f"VOLUME: Volume file not found after creation: {volume_path}")
raise VolumeCreationError(f"Volume file not found after creation: {volume_path}")
volume_size = os.path.getsize(volume_path)
logger.info(f"VOLUME: Volume created: {volume_path} ({volume_size} bytes)")
return volume_path
def attach_volume_to_vm(conn, vm_name, volume_path, logger):
"""
Attach the volume to the VM's libvirt XML configuration.
Args:
conn: Libvirt connection
vm_name: Name of the VM
volume_path: Path to the volume file
logger: Logger instance
Raises:
VolumeAttachmentError: If volume attachment fails
"""
try:
# Get the VM domain
dom = conn.lookupByName(vm_name)
# Get the XML description of the VM
xml_desc = dom.XMLDesc()
root = ET.fromstring(xml_desc)
# Find the devices element
devices_elem = root.find('./devices')
if devices_elem is None:
logger.error("VM: Could not find <devices> element in XML")
raise VolumeAttachmentError("Could not find <devices> element in VM XML")
# Log ALL devices with PCI addresses to find conflicts
logger.info("DISK_DEBUG: Examining ALL devices with PCI addresses")
for device in devices_elem:
address = device.find('./address')
if address is not None and address.get('type') == 'pci':
bus = address.get('bus', 'unknown')
slot = address.get('slot', 'unknown')
function = address.get('function', 'unknown')
logger.info(f"DISK_DEBUG: Device {device.tag}: bus={bus}, slot={slot}, function={function}")
# Log existing disk configuration for debugging
logger.info("DISK_DEBUG: Examining existing disk configuration")
existing_disks = devices_elem.findall('./disk')
for idx, disk in enumerate(existing_disks):
target = disk.find('./target')
source = disk.find('./source')
address = disk.find('./address')
dev_name = target.get('dev') if target is not None else 'unknown'
source_file = source.get('file') if source is not None else 'unknown'
if address is not None:
slot = address.get('slot', 'unknown')
bus = address.get('bus', 'unknown')
logger.info(f"DISK_DEBUG: Disk {idx}: dev={dev_name}, source={source_file}, slot={slot}, bus={bus}")
else:
logger.info(f"DISK_DEBUG: Disk {idx}: dev={dev_name}, source={source_file}, no address element")
# Check if vdb already exists
for disk in devices_elem.findall('./disk'):
target = disk.find('./target')
if target is not None and target.get('dev') == 'vdb':
logger.error("VM: Device vdb already exists in VM configuration")
raise VolumeAttachmentError("Device vdb already exists in VM configuration")
logger.info(f"VM: Attaching volume to {vm_name} as /dev/vdb")
# Create disk element
disk_elem = ET.SubElement(devices_elem, 'disk', attrib={
'type': 'file',
'device': 'disk'
})
# Add driver element
ET.SubElement(disk_elem, 'driver', attrib={
'name': 'qemu',
'type': 'raw',
'cache': 'none',
'io': 'native'
})
# Add source element
ET.SubElement(disk_elem, 'source', attrib={
'file': volume_path
})
# Add target element
ET.SubElement(disk_elem, 'target', attrib={
'dev': 'vdb',
'bus': 'virtio'
})
# Add address element
# Use bus 0x07 with slot 0x00 to ensure NSM volume appears after OS disk (which is on bus 0x04)
# Bus 0x05 is used by memballoon, bus 0x06 is used by rng device
# Libvirt requires slot <= 0 for non-zero buses
# This ensures vda = OS disk, vdb = NSM volume
ET.SubElement(disk_elem, 'address', attrib={
'type': 'pci',
'domain': '0x0000',
'bus': '0x07',
'slot': '0x00',
'function': '0x0'
})
logger.info(f"HARDWARE: Added disk configuration for vdb")
# Log disk ordering after adding new disk
logger.info("DISK_DEBUG: Disk configuration after adding NSM volume")
all_disks = devices_elem.findall('./disk')
for idx, disk in enumerate(all_disks):
target = disk.find('./target')
source = disk.find('./source')
address = disk.find('./address')
dev_name = target.get('dev') if target is not None else 'unknown'
source_file = source.get('file') if source is not None else 'unknown'
if address is not None:
slot = address.get('slot', 'unknown')
bus = address.get('bus', 'unknown')
logger.info(f"DISK_DEBUG: Disk {idx}: dev={dev_name}, source={source_file}, slot={slot}, bus={bus}")
else:
logger.info(f"DISK_DEBUG: Disk {idx}: dev={dev_name}, source={source_file}, no address element")
# Convert XML back to string
new_xml_desc = ET.tostring(root, encoding='unicode')
# Redefine the VM with the new XML
conn.defineXML(new_xml_desc)
logger.info(f"VM: VM redefined with volume attached")
except libvirt.libvirtError as e:
logger.error(f"VM: Failed to attach volume: {e}")
raise VolumeAttachmentError(f"Failed to attach volume: {e}")
except Exception as e:
logger.error(f"VM: Failed to attach volume: {e}")
raise VolumeAttachmentError(f"Failed to attach volume: {e}")
def emit_status_event(vm_name, status):
"""
Emit a deployment status event.
Args:
vm_name: Name of the VM
status: Status message
"""
try:
subprocess.run([
'so-salt-emit-vm-deployment-status-event',
'-v', vm_name,
'-H', HYPERVISOR,
'-s', status
], check=True)
except subprocess.CalledProcessError as e:
# Don't fail the entire operation if status event fails
pass
def main():
"""Main function to orchestrate volume creation and attachment."""
# Set up logging using the so_logging_utils library
string_handler = StringIOHandler()
string_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
logger = setup_logging(
logger_name='so-kvm-create-volume',
log_file_path='/opt/so/log/hypervisor/so-kvm-create-volume.log',
log_level=logging.INFO,
format_str='%(asctime)s - %(levelname)s - %(message)s'
)
logger.addHandler(string_handler)
vm_name = None
try:
# Parse arguments
args = parse_arguments()
vm_name = args.vm
size_gb = args.size
start_vm_flag = args.start
logger.info(f"VOLUME: Starting volume creation for VM '{vm_name}' with size {size_gb} GB")
# Emit start status event
emit_status_event(vm_name, 'Volume Creation')
# Ensure volume directory exists before checking disk space
try:
os.makedirs(VOLUME_DIR, mode=0o754, exist_ok=True)
qemu_uid = pwd.getpwnam('qemu').pw_uid
qemu_gid = grp.getgrnam('qemu').gr_gid
os.chown(VOLUME_DIR, qemu_uid, qemu_gid)
logger.debug(f"VOLUME: Ensured volume directory exists: {VOLUME_DIR}")
except Exception as e:
logger.error(f"VOLUME: Failed to create volume directory: {e}")
emit_status_event(vm_name, 'Volume Configuration Failed')
sys.exit(1)
# Check disk space
check_disk_space(size_gb, logger)
# Connect to libvirt
try:
conn = libvirt.open(None)
logger.info("VM: Connected to libvirt")
except libvirt.libvirtError as e:
logger.error(f"VM: Failed to open connection to libvirt: {e}")
emit_status_event(vm_name, 'Volume Configuration Failed')
sys.exit(1)
# Stop VM if running
dom = stop_vm(conn, vm_name, logger)
# Create volume file
volume_path = create_volume_file(vm_name, size_gb, logger)
# Attach volume to VM
attach_volume_to_vm(conn, vm_name, volume_path, logger)
# Start VM if -S or --start argument is provided
if start_vm_flag:
dom = conn.lookupByName(vm_name)
start_vm(dom, logger)
logger.info(f"VM: VM '{vm_name}' started successfully")
else:
logger.info("VM: Start flag not provided; VM will remain stopped")
# Close connection
conn.close()
# Emit success status event
emit_status_event(vm_name, 'Volume Configuration')
logger.info(f"VOLUME: Volume creation and attachment completed successfully for VM '{vm_name}'")
except KeyboardInterrupt:
error_msg = "Operation cancelled by user"
logger.error(error_msg)
if vm_name:
emit_status_event(vm_name, 'Volume Configuration Failed')
sys.exit(1)
except InsufficientSpaceError as e:
error_msg = f"SPACE: {str(e)}"
logger.error(error_msg)
if vm_name:
emit_status_event(vm_name, 'Volume Configuration Failed')
sys.exit(1)
except VolumeCreationError as e:
error_msg = f"VOLUME: {str(e)}"
logger.error(error_msg)
if vm_name:
emit_status_event(vm_name, 'Volume Configuration Failed')
sys.exit(1)
except VolumeAttachmentError as e:
error_msg = f"VM: {str(e)}"
logger.error(error_msg)
if vm_name:
emit_status_event(vm_name, 'Volume Configuration Failed')
sys.exit(1)
except Exception as e:
error_msg = f"An error occurred: {str(e)}"
logger.error(error_msg)
if vm_name:
emit_status_event(vm_name, 'Volume Configuration Failed')
sys.exit(1)
if __name__ == '__main__':
main()
{%- else -%}
echo "Hypervisor nodes are a feature supported only for customers with a valid license. \
Contact Security Onion Solutions, LLC via our website at https://securityonionsolutions.com \
for more information about purchasing a license to enable this feature."
{% endif -%}

File diff suppressed because one or more lines are too long

View File

@@ -22,7 +22,7 @@ kibana:
- default
- file
migrations:
discardCorruptObjects: "8.18.8"
discardCorruptObjects: "8.18.6"
telemetry:
enabled: False
security:

View File

@@ -54,9 +54,6 @@ so-kratos:
- file: kratosconfig
- file: kratoslogdir
- file: kratosdir
- retry:
attempts: 10
interval: 10
delete_so-kratos_so-status.disabled:
file.uncomment:

View File

@@ -4,9 +4,6 @@
# Elastic License 2.0.
# We do not import GLOBALS in this state because it is called during setup
include:
- salt.minion.service_file
- salt.mine_functions
down_original_mgmt_interface:
cmd.run:
@@ -31,14 +28,29 @@ wait_for_br0_ip:
- timeout: 95
- onchanges:
- cmd: down_original_mgmt_interface
- onchanges_in:
- file: salt_minion_service_unit_file
- file: mine_functions
{% if grains.role == 'so-hypervisor' %}
update_mine_functions:
file.managed:
- name: /etc/salt/minion.d/mine_functions.conf
- contents: |
mine_interval: 25
mine_functions:
network.ip_addrs:
- interface: br0
{%- if role in ['so-eval','so-import','so-manager','so-managerhype','so-managersearch','so-standalone'] %}
x509.get_pem_entries:
- glob_path: '/etc/pki/ca.crt'
{% endif %}
- onchanges:
- cmd: wait_for_br0_ip
restart_salt_minion_service:
service.running:
- name: salt-minion
- enable: True
- listen:
- file: salt_minion_service_unit_file
- file: mine_functions
- file: update_mine_functions
{% endif %}

View File

@@ -31,19 +31,6 @@ libvirt_conf_dir:
- group: 939
- makedirs: True
libvirt_volumes:
file.directory:
- name: /nsm/libvirt/volumes
- user: qemu
- group: qemu
- dir_mode: 755
- file_mode: 640
- recurse:
- user
- group
- mode
- makedirs: True
libvirt_config:
file.managed:
- name: /opt/so/conf/libvirt/libvirtd.conf

View File

@@ -1,3 +0,0 @@
#!/bin/bash
curl -s -L http://localhost:9600/_node/stats/flow | jq

View File

@@ -1,3 +0,0 @@
#!/bin/bash
curl -s -L http://localhost:9600/_health_report | jq

View File

@@ -1,3 +0,0 @@
#!/bin/bash
curl -s -L http://localhost:9600/_node/stats/jvm | jq

View File

@@ -206,6 +206,7 @@ git_config_set_safe_dirs:
- multivar:
- /nsm/rules/custom-local-repos/local-sigma
- /nsm/rules/custom-local-repos/local-yara
- /nsm/rules/custom-local-repos/local-playbooks
- /nsm/securityonion-resources
- /opt/so/conf/soc/ai_summary_repos/securityonion-resources
- /nsm/airgap-resources/playbooks

View File

@@ -5,12 +5,10 @@
# https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0.
default_salt_dir=/opt/so/saltstack/default
VERBOSE=0
VERY_VERBOSE=0
TEST_MODE=0
default_salt_dir=/opt/so/saltstack/default
clone_to_tmp() {
# TODO Need to add a air gap option
# Make a temp location for the files
mkdir /tmp/sogh
@@ -18,110 +16,19 @@ clone_to_tmp() {
#git clone -b dev https://github.com/Security-Onion-Solutions/securityonion.git
git clone https://github.com/Security-Onion-Solutions/securityonion.git
cd /tmp
}
show_file_changes() {
local source_dir="$1"
local dest_dir="$2"
local dir_type="$3" # "salt" or "pillar"
if [ $VERBOSE -eq 0 ]; then
return
fi
echo "=== Changes for $dir_type directory ==="
# Find all files in source directory
if [ -d "$source_dir" ]; then
find "$source_dir" -type f | while read -r source_file; do
# Get relative path
rel_path="${source_file#$source_dir/}"
dest_file="$dest_dir/$rel_path"
if [ ! -f "$dest_file" ]; then
echo "ADDED: $dest_file"
if [ $VERY_VERBOSE -eq 1 ]; then
echo " (New file - showing first 20 lines)"
head -n 20 "$source_file" | sed 's/^/ + /'
echo ""
fi
elif ! cmp -s "$source_file" "$dest_file"; then
echo "MODIFIED: $dest_file"
if [ $VERY_VERBOSE -eq 1 ]; then
echo " (Changes:)"
diff -u "$dest_file" "$source_file" | sed 's/^/ /'
echo ""
fi
fi
done
fi
# Find deleted files (exist in dest but not in source)
if [ -d "$dest_dir" ]; then
find "$dest_dir" -type f | while read -r dest_file; do
# Get relative path
rel_path="${dest_file#$dest_dir/}"
source_file="$source_dir/$rel_path"
if [ ! -f "$source_file" ]; then
echo "DELETED: $dest_file"
if [ $VERY_VERBOSE -eq 1 ]; then
echo " (File was deleted)"
echo ""
fi
fi
done
fi
echo ""
}
copy_new_files() {
# Copy new files over to the salt dir
cd /tmp/sogh/securityonion
git checkout $BRANCH
VERSION=$(cat VERSION)
if [ $TEST_MODE -eq 1 ]; then
echo "=== TEST MODE: Showing what would change without making changes ==="
echo "Branch: $BRANCH"
echo "Version: $VERSION"
echo ""
fi
# Show changes before copying if verbose mode is enabled OR if in test mode
if [ $VERBOSE -eq 1 ] || [ $TEST_MODE -eq 1 ]; then
if [ $TEST_MODE -eq 1 ]; then
# In test mode, force at least basic verbose output
local old_verbose=$VERBOSE
if [ $VERBOSE -eq 0 ]; then
VERBOSE=1
fi
fi
echo "Analyzing file changes..."
show_file_changes "$(pwd)/salt" "$default_salt_dir/salt" "salt"
show_file_changes "$(pwd)/pillar" "$default_salt_dir/pillar" "pillar"
if [ $TEST_MODE -eq 1 ] && [ $old_verbose -eq 0 ]; then
# Restore original verbose setting
VERBOSE=$old_verbose
fi
fi
# If in test mode, don't copy files
if [ $TEST_MODE -eq 1 ]; then
echo "=== TEST MODE: No files were modified ==="
echo "To apply these changes, run without --test option"
rm -rf /tmp/sogh
return
fi
# We need to overwrite if there is a repo file
if [ -d /opt/so/repo ]; then
tar -czf /opt/so/repo/"$VERSION".tar.gz -C "$(pwd)/.." .
fi
rsync -a salt $default_salt_dir/
rsync -a pillar $default_salt_dir/
chown -R socore:socore $default_salt_dir/salt
@@ -138,64 +45,11 @@ got_root(){
fi
}
show_usage() {
echo "Usage: $0 [-v] [-vv] [--test] [branch]"
echo " -v Show verbose output (files changed/added/deleted)"
echo " -vv Show very verbose output (includes file diffs)"
echo " --test Test mode - show what would change without making changes"
echo " branch Git branch to checkout (default: 2.4/main)"
echo ""
echo "Examples:"
echo " $0 # Normal operation"
echo " $0 -v # Show which files change"
echo " $0 -vv # Show files and their diffs"
echo " $0 --test # See what would change (dry run)"
echo " $0 --test -vv # Test mode with detailed diffs"
echo " $0 -v dev-branch # Use specific branch with verbose output"
exit 1
}
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-v)
VERBOSE=1
shift
;;
-vv)
VERBOSE=1
VERY_VERBOSE=1
shift
;;
--test)
TEST_MODE=1
shift
;;
-h|--help)
show_usage
;;
-*)
echo "Unknown option $1"
show_usage
;;
*)
# This should be the branch name
if [ -z "$BRANCH" ]; then
BRANCH="$1"
else
echo "Too many arguments"
show_usage
fi
shift
;;
esac
done
# Set default branch if not provided
if [ -z "$BRANCH" ]; then
BRANCH=2.4/main
fi
got_root
if [ $# -ne 1 ] ; then
BRANCH=2.4/main
else
BRANCH=$1
fi
clone_to_tmp
copy_new_files

View File

@@ -387,7 +387,7 @@ function syncElastic() {
if [[ -z "$SKIP_STATE_APPLY" ]]; then
echo "Elastic state will be re-applied to affected minions. This will run in the background and may take several minutes to complete."
echo "Applying elastic state to elastic minions at $(date)" >> /opt/so/log/soc/sync.log 2>&1
salt --async -C 'I@elasticsearch:enabled:true' state.apply elasticsearch queue=True >> /opt/so/log/soc/sync.log 2>&1
salt --async -C 'G@role:so-standalone or G@role:so-eval or G@role:so-import or G@role:so-manager or G@role:so-managersearch or G@role:so-searchnode or G@role:so-heavynode' state.apply elasticsearch queue=True >> /opt/so/log/soc/sync.log 2>&1
fi
else
echo "Newly generated users/roles files are incomplete; aborting."

View File

@@ -26,8 +26,8 @@ def showUsage(args):
print(' Where:', file=sys.stderr)
print(' YAML_FILE - Path to the file that will be modified. Ex: /opt/so/conf/service/conf.yaml', file=sys.stderr)
print(' KEY - YAML key, does not support \' or " characters at this time. Ex: level1.level2', file=sys.stderr)
print(' VALUE - Value to set for a given key. Can be a literal value or file:<path> to load from a YAML file.', file=sys.stderr)
print(' LISTITEM - Item to append to a given key\'s list value. Can be a literal value or file:<path> to load from a YAML file.', file=sys.stderr)
print(' VALUE - Value to set for a given key', file=sys.stderr)
print(' LISTITEM - Item to append to a given key\'s list value', file=sys.stderr)
sys.exit(1)
@@ -58,13 +58,7 @@ def appendItem(content, key, listItem):
def convertType(value):
if isinstance(value, str) and value.startswith("file:"):
path = value[5:] # Remove "file:" prefix
if not os.path.exists(path):
print(f"File '{path}' does not exist.", file=sys.stderr)
sys.exit(1)
return loadYaml(path)
elif isinstance(value, str) and len(value) > 0 and (not value.startswith("0") or len(value) == 1):
if isinstance(value, str) and len(value) > 0 and (not value.startswith("0") or len(value) == 1):
if "." in value:
try:
value = float(value)

View File

@@ -361,29 +361,6 @@ class TestRemove(unittest.TestCase):
self.assertEqual(soyaml.convertType("FALSE"), False)
self.assertEqual(soyaml.convertType(""), "")
def test_convert_file(self):
import tempfile
import os
# Create a temporary YAML file
with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
f.write("test:\n - name: hi\n color: blue\n")
temp_file = f.name
try:
result = soyaml.convertType(f"file:{temp_file}")
expected = {"test": [{"name": "hi", "color": "blue"}]}
self.assertEqual(result, expected)
finally:
os.unlink(temp_file)
def test_convert_file_nonexistent(self):
with self.assertRaises(SystemExit) as cm:
with patch('sys.stderr', new=StringIO()) as mock_stderr:
soyaml.convertType("file:/nonexistent/file.yaml")
self.assertEqual(cm.exception.code, 1)
self.assertIn("File '/nonexistent/file.yaml' does not exist.", mock_stderr.getvalue())
def test_get_int(self):
with patch('sys.stdout', new=StringIO()) as mock_stdout:
filename = "/tmp/so-yaml_test-get.yaml"

View File

@@ -21,9 +21,6 @@ whiptail_title='Security Onion UPdater'
NOTIFYCUSTOMELASTICCONFIG=false
TOPFILE=/opt/so/saltstack/default/salt/top.sls
BACKUPTOPFILE=/opt/so/saltstack/default/salt/top.sls.backup
SALTUPGRADED=false
SALT_CLOUD_INSTALLED=false
SALT_CLOUD_CONFIGURED=false
# used to display messages to the user at the end of soup
declare -a FINAL_MESSAGE_QUEUE=()
@@ -172,8 +169,6 @@ airgap_update_dockers() {
tar xf "$AGDOCKER/registry.tar" -C /nsm/docker-registry/docker
echo "Add Registry back"
docker load -i "$AGDOCKER/registry_image.tar"
echo "Restart registry container"
salt-call state.apply registry queue=True
fi
fi
}
@@ -274,7 +269,7 @@ check_os_updates() {
if [[ "$confirm" == [cC] ]]; then
echo "Continuing without updating packages"
elif [[ "$confirm" == [uU] ]]; then
echo "Applying Grid Updates. The following patch.os salt state may take a while depending on how many packages need to be updated."
echo "Applying Grid Updates"
update_flag=true
else
echo "Exiting soup"
@@ -425,7 +420,6 @@ preupgrade_changes() {
[[ "$INSTALLEDVERSION" == 2.4.150 ]] && up_to_2.4.160
[[ "$INSTALLEDVERSION" == 2.4.160 ]] && up_to_2.4.170
[[ "$INSTALLEDVERSION" == 2.4.170 ]] && up_to_2.4.180
[[ "$INSTALLEDVERSION" == 2.4.180 ]] && up_to_2.4.190
true
}
@@ -456,7 +450,6 @@ postupgrade_changes() {
[[ "$POSTVERSION" == 2.4.150 ]] && post_to_2.4.160
[[ "$POSTVERSION" == 2.4.160 ]] && post_to_2.4.170
[[ "$POSTVERSION" == 2.4.170 ]] && post_to_2.4.180
[[ "$POSTVERSION" == 2.4.180 ]] && post_to_2.4.190
true
}
@@ -606,36 +599,15 @@ post_to_2.4.170() {
}
post_to_2.4.180() {
echo "Regenerating Elastic Agent Installers"
/sbin/so-elastic-agent-gen-installers
# Force update to Kafka output policy
/usr/sbin/so-kafka-fleet-output-policy --force
POSTVERSION=2.4.180
}
post_to_2.4.190() {
echo "Regenerating Elastic Agent Installers"
/sbin/so-elastic-agent-gen-installers
# Only need to update import / eval nodes
if [[ "$MINION_ROLE" == "import" ]] || [[ "$MINION_ROLE" == "eval" ]]; then
update_import_fleet_output
fi
# Check if expected default policy is logstash (global.pipeline is REDIS or "")
pipeline=$(lookup_pillar "pipeline" "global")
if [[ -z "$pipeline" ]] || [[ "$pipeline" == "REDIS" ]]; then
# Check if this grid is currently affected by corrupt fleet output policy
if elastic-agent status | grep "config: key file not configured" > /dev/null 2>&1; then
echo "Elastic Agent shows an ssl error connecting to logstash output. Updating output policy..."
update_default_logstash_output
fi
fi
# Apply new elasticsearch.server index template
rollover_index "logs-elasticsearch.server-default"
POSTVERSION=2.4.190
}
repo_sync() {
echo "Sync the local repo."
su socore -c '/usr/sbin/so-repo-sync' || fail "Unable to complete so-repo-sync."
@@ -892,15 +864,10 @@ up_to_2.4.170() {
}
up_to_2.4.180() {
echo "Nothing to do for 2.4.180"
INSTALLEDVERSION=2.4.180
}
up_to_2.4.190() {
# Elastic Update for this release, so download Elastic Agent files
determine_elastic_agent_upgrade
INSTALLEDVERSION=2.4.190
INSTALLEDVERSION=2.4.180
}
add_hydra_pillars() {
@@ -1176,44 +1143,6 @@ update_elasticsearch_index_settings() {
done
}
update_import_fleet_output() {
if output=$(curl -sK /opt/so/conf/elasticsearch/curl.config -L "localhost:5601/api/fleet/outputs/so-manager_elasticsearch" --retry 3 --fail 2>/dev/null); then
# Update the current config of so-manager_elasticsearch output policy in place (leaving any customizations like having changed the preset value from 'balanced' to 'performance')
CAFINGERPRINT=$(openssl x509 -in /etc/pki/tls/certs/intca.crt -outform DER | sha256sum | cut -d' ' -f1 | tr '[:lower:]' '[:upper:]')
updated_policy=$(jq --arg CAFINGERPRINT "$CAFINGERPRINT" '.item | (del(.id) | .ca_trusted_fingerprint = $CAFINGERPRINT)' <<< "$output")
if curl -sK /opt/so/conf/elasticsearch/curl.config -L "localhost:5601/api/fleet/outputs/so-manager_elasticsearch" -XPUT -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$updated_policy" --retry 3 --fail 2>/dev/null; then
echo "Successfully updated so-manager_elasticsearch fleet output policy"
else
fail "Failed to update so-manager_elasticsearch fleet output policy"
fi
fi
}
update_default_logstash_output() {
echo "Updating fleet logstash output policy grid-logstash"
if logstash_policy=$(curl -K /opt/so/conf/elasticsearch/curl.config -L "http://localhost:5601/api/fleet/outputs/so-manager_logstash" --retry 3 --retry-delay 10 --fail 2>/dev/null); then
# Keep already configured hosts for this update, subsequent host updates come from so-elastic-fleet-outputs-update
HOSTS=$(echo "$logstash_policy" | jq -r '.item.hosts')
DEFAULT_ENABLED=$(echo "$logstash_policy" | jq -r '.item.is_default')
DEFAULT_MONITORING_ENABLED=$(echo "$logstash_policy" | jq -r '.item.is_default_monitoring')
LOGSTASHKEY=$(openssl rsa -in /etc/pki/elasticfleet-logstash.key)
LOGSTASHCRT=$(openssl x509 -in /etc/pki/elasticfleet-logstash.crt)
LOGSTASHCA=$(openssl x509 -in /etc/pki/tls/certs/intca.crt)
JSON_STRING=$(jq -n \
--argjson HOSTS "$HOSTS" \
--arg DEFAULT_ENABLED "$DEFAULT_ENABLED" \
--arg DEFAULT_MONITORING_ENABLED "$DEFAULT_MONITORING_ENABLED" \
--arg LOGSTASHKEY "$LOGSTASHKEY" \
--arg LOGSTASHCRT "$LOGSTASHCRT" \
--arg LOGSTASHCA "$LOGSTASHCA" \
'{"name":"grid-logstash","type":"logstash","hosts": $HOSTS,"is_default": $DEFAULT_ENABLED,"is_default_monitoring": $DEFAULT_MONITORING_ENABLED,"config_yaml":"","ssl":{"certificate": $LOGSTASHCRT,"certificate_authorities":[ $LOGSTASHCA ]},"secrets":{"ssl":{"key": $LOGSTASHKEY }}}')
fi
if curl -K /opt/so/conf/elasticsearch/curl.config -L -X PUT "localhost:5601/api/fleet/outputs/so-manager_logstash" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING" --retry 3 --retry-delay 10 --fail; then
echo "Successfully updated grid-logstash fleet output policy"
fi
}
update_salt_mine() {
echo "Populating the mine with mine_functions for each host."
set +e
@@ -1263,43 +1192,24 @@ upgrade_check_salt() {
}
upgrade_salt() {
SALTUPGRADED=True
echo "Performing upgrade of Salt from $INSTALLEDSALTVERSION to $NEWSALTVERSION."
echo ""
# If rhel family
if [[ $is_rpm ]]; then
# 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 ""
yum versionlock delete "salt"
yum versionlock delete "salt-minion"
yum versionlock delete "salt-master"
# Remove salt-cloud versionlock if installed
if [[ $SALT_CLOUD_INSTALLED == true ]]; then
yum versionlock delete "salt-cloud"
fi
echo "Updating Salt packages."
echo ""
set +e
# if oracle run with -r to ignore repos set by bootstrap
if [[ $OS == 'oracle' ]]; then
# Add -L flag only if salt-cloud is already installed
if [[ $SALT_CLOUD_INSTALLED == true ]]; then
run_check_net_err \
"sh $UPDATE_DIR/salt/salt/scripts/bootstrap-salt.sh -X -r -L -F -M stable \"$NEWSALTVERSION\"" \
"Could not update salt, please check $SOUP_LOG for details."
else
run_check_net_err \
"sh $UPDATE_DIR/salt/salt/scripts/bootstrap-salt.sh -X -r -F -M stable \"$NEWSALTVERSION\"" \
"Could not update salt, please check $SOUP_LOG for details."
fi
# if another rhel family variant we want to run without -r to allow the bootstrap script to manage repos
else
run_check_net_err \
@@ -1312,14 +1222,8 @@ upgrade_salt() {
yum versionlock add "salt-0:$NEWSALTVERSION-0.*"
yum versionlock add "salt-minion-0:$NEWSALTVERSION-0.*"
yum versionlock add "salt-master-0:$NEWSALTVERSION-0.*"
# Add salt-cloud versionlock if installed
if [[ $SALT_CLOUD_INSTALLED == true ]]; then
yum versionlock add "salt-cloud-0:$NEWSALTVERSION-0.*"
fi
# Else do Ubuntu things
elif [[ $is_deb ]]; then
# ensure these files don't exist when upgrading from 3006.9 to 3006.16
rm -f /etc/apt/keyrings/salt-archive-keyring-2023.pgp /etc/apt/sources.list.d/salt.list
echo "Removing apt hold for Salt."
echo ""
apt-mark unhold "salt-common"
@@ -1350,7 +1254,6 @@ upgrade_salt() {
echo ""
exit 1
else
SALTUPGRADED=true
echo "Salt upgrade success."
echo ""
fi
@@ -1456,7 +1359,6 @@ main() {
fi
set_minionid
MINION_ROLE=$(lookup_role)
echo "Found that Security Onion $INSTALLEDVERSION is currently installed."
echo ""
if [[ $is_airgap -eq 0 ]]; then
@@ -1499,7 +1401,7 @@ main() {
if [ "$is_hotfix" == "true" ]; then
echo "Applying $HOTFIXVERSION hotfix"
# 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 [[ ! "$MINIONID" =~ "_import" ]]; then
backup_old_states_pillars
fi
copy_new_files
@@ -1562,7 +1464,7 @@ main() {
fi
# 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 [[ ! "$MINIONID" =~ "_import" ]]; then
echo ""
echo "Creating snapshots of default and local Salt states and pillars and saving to /nsm/backup/"
backup_old_states_pillars
@@ -1594,11 +1496,6 @@ main() {
# ensure the mine is updated and populated before highstates run, following the salt-master restart
update_salt_mine
if [[ $SALT_CLOUD_CONFIGURED == true && $SALTUPGRADED == true ]]; then
echo "Updating salt-cloud config to use the new Salt version"
salt-call state.apply salt.cloud.config concurrent=True
fi
enable_highstate
echo ""
@@ -1681,7 +1578,7 @@ This appears to be a distributed deployment. Other nodes should update themselve
Each minion is on a random 15 minute check-in period and things like network bandwidth can be a factor in how long the actual upgrade takes. If you have a heavy node on a slow link, it is going to take a while to get the containers to it. Depending on what changes happened between the versions, Elasticsearch might not be able to talk to said heavy node until the update is complete.
If it looks like youre missing data after the upgrade, please avoid restarting services and instead make sure at least one search node has completed its upgrade. The best way to do this is to run 'sudo salt-call state.highstate' from a search node and make sure there are no errors. Typically if it works on one node it will work on the rest. Sensor nodes are less complex and will update as they check in so you can monitor those from the Grid section of SOC.
If it looks like youre missing data after the upgrade, please avoid restarting services and instead make sure at least one search node has completed its upgrade. The best way to do this is to run 'sudo salt-call state.highstate' from a search node and make sure there are no errors. Typically if it works on one node it will work on the rest. Forward nodes are less complex and will update as they check in so you can monitor those from the Grid section of SOC.
For more information, please see $DOC_BASE_URL/soup.html#distributed-deployments.

View File

@@ -211,7 +211,7 @@ Exit Codes:
Logging:
- Logs are written to /opt/so/log/salt/so-salt-cloud.
- Logs are written to /opt/so/log/salt/so-salt-cloud.log.
- Both file and console logging are enabled for real-time monitoring.
"""
@@ -233,7 +233,7 @@ local = salt.client.LocalClient()
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
file_handler = logging.FileHandler('/opt/so/log/salt/so-salt-cloud')
file_handler = logging.FileHandler('/opt/so/log/salt/so-salt-cloud.log')
console_handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(message)s')
@@ -516,85 +516,23 @@ def run_qcow2_modify_hardware_config(profile, vm_name, cpu=None, memory=None, pc
target = hv_name + "_*"
try:
args_list = ['vm_name=' + vm_name]
# Only add parameters that are actually specified
if cpu is not None:
args_list.append('cpu=' + str(cpu))
if memory is not None:
args_list.append('memory=' + str(memory))
args_list = [
'vm_name=' + vm_name,
'cpu=' + str(cpu) if cpu else '',
'memory=' + str(memory) if memory else '',
'start=' + str(start)
]
# Add PCI devices if provided
if pci_list:
# Pass all PCI devices as a comma-separated list
args_list.append('pci=' + ','.join(pci_list))
# Always add start parameter
args_list.append('start=' + str(start))
result = local.cmd(target, 'qcow2.modify_hardware_config', args_list)
format_qcow2_output('Hardware configuration', result)
except Exception as e:
logger.error(f"An error occurred while running qcow2.modify_hardware_config: {e}")
def run_qcow2_create_volume_config(profile, vm_name, size_gb, cpu=None, memory=None, start=False):
"""Create a volume for the VM and optionally configure CPU/memory.
Args:
profile (str): The cloud profile name
vm_name (str): The name of the VM
size_gb (int): Size of the volume in GB
cpu (int, optional): Number of CPUs to assign
memory (int, optional): Amount of memory in MiB
start (bool): Whether to start the VM after configuration
"""
hv_name = profile.split('_')[1]
target = hv_name + "_*"
try:
# Step 1: Create the volume
logger.info(f"Creating {size_gb}GB volume for VM {vm_name}")
volume_result = local.cmd(
target,
'qcow2.create_volume_config',
kwarg={
'vm_name': vm_name,
'size_gb': size_gb,
'start': False # Don't start yet if we need to configure CPU/memory
}
)
format_qcow2_output('Volume creation', volume_result)
# Step 2: Configure CPU and memory if specified
if cpu or memory:
logger.info(f"Configuring hardware for VM {vm_name}: CPU={cpu}, Memory={memory}MiB")
hw_result = local.cmd(
target,
'qcow2.modify_hardware_config',
kwarg={
'vm_name': vm_name,
'cpu': cpu,
'memory': memory,
'start': start
}
)
format_qcow2_output('Hardware configuration', hw_result)
elif start:
# If no CPU/memory config needed but we need to start the VM
logger.info(f"Starting VM {vm_name}")
start_result = local.cmd(
target,
'qcow2.modify_hardware_config',
kwarg={
'vm_name': vm_name,
'start': True
}
)
format_qcow2_output('VM startup', start_result)
except Exception as e:
logger.error(f"An error occurred while creating volume and configuring hardware: {e}")
def run_qcow2_modify_network_config(profile, vm_name, mode, ip=None, gateway=None, dns=None, search_domain=None):
hv_name = profile.split('_')[1]
target = hv_name + "_*"
@@ -648,7 +586,6 @@ def parse_arguments():
network_group.add_argument('-c', '--cpu', type=int, help='Number of virtual CPUs to assign.')
network_group.add_argument('-m', '--memory', type=int, help='Amount of memory to assign in MiB.')
network_group.add_argument('-P', '--pci', action='append', help='PCI hardware ID(s) to passthrough to the VM (e.g., 0000:c7:00.0). Can be specified multiple times.')
network_group.add_argument('--nsm-size', type=int, help='Size in GB for NSM volume creation. Can be used with copper/sfp NICs (--pci). Only disk passthrough (without --nsm-size) prevents volume creation.')
args = parser.parse_args()
@@ -684,8 +621,6 @@ def main():
hw_config.append(f"{args.memory}MB RAM")
if args.pci:
hw_config.append(f"PCI devices: {', '.join(args.pci)}")
if args.nsm_size:
hw_config.append(f"NSM volume: {args.nsm_size}GB")
hw_string = f" and hardware config: {', '.join(hw_config)}" if hw_config else ""
logger.info(f"Received request to create VM '{args.vm_name}' using profile '{args.profile}' {network_config}{hw_string}")
@@ -708,58 +643,8 @@ def main():
# Step 2: Provision the VM (without starting it)
call_salt_cloud(args.profile, args.vm_name)
# Step 3: Determine storage configuration approach
# Priority: disk passthrough > volume creation (but volume can coexist with copper/sfp NICs)
# Note: virtual_node_manager.py already filters out --nsm-size when disk is present,
# so if both --pci and --nsm-size are present here, the PCI devices are copper/sfp NICs
use_passthrough = False
use_volume_creation = False
has_nic_passthrough = False
if args.nsm_size:
# Validate nsm_size
if args.nsm_size <= 0:
logger.error(f"Invalid nsm_size value: {args.nsm_size}. Must be a positive integer.")
sys.exit(1)
use_volume_creation = True
logger.info(f"Using volume creation with size {args.nsm_size}GB (--nsm-size parameter specified)")
if args.pci:
# If both nsm_size and PCI are present, PCI devices are copper/sfp NICs
# (virtual_node_manager.py filters out nsm_size when disk is present)
has_nic_passthrough = True
logger.info(f"PCI devices (copper/sfp NICs) will be passed through along with volume: {', '.join(args.pci)}")
elif args.pci:
# Only PCI devices, no nsm_size - could be disk or NICs
# this script is called by virtual_node_manager and that strips any possibility that nsm_size and the disk pci slot is sent to this script
# we might have not specified a disk passthrough or nsm_size, but pass another pci slot and we end up here
use_passthrough = True
logger.info(f"Configuring PCI device passthrough.(--pci parameter specified without --nsm-size)")
# Step 4: Configure hardware based on storage approach
if use_volume_creation:
# Create volume first
run_qcow2_create_volume_config(args.profile, args.vm_name, size_gb=args.nsm_size, cpu=args.cpu, memory=args.memory, start=False)
# Then configure NICs if present
if has_nic_passthrough:
logger.info(f"Configuring NIC passthrough for VM {args.vm_name}")
run_qcow2_modify_hardware_config(args.profile, args.vm_name, cpu=None, memory=None, pci_list=args.pci, start=True)
else:
# No NICs, just start the VM
logger.info(f"Starting VM {args.vm_name}")
run_qcow2_modify_hardware_config(args.profile, args.vm_name, cpu=None, memory=None, pci_list=None, start=True)
elif use_passthrough:
# Use existing passthrough logic via modify_hardware_config
# Step 3: Modify hardware configuration
run_qcow2_modify_hardware_config(args.profile, args.vm_name, cpu=args.cpu, memory=args.memory, pci_list=args.pci, start=True)
else:
# No storage configuration, just configure CPU/memory if specified
if args.cpu or args.memory:
run_qcow2_modify_hardware_config(args.profile, args.vm_name, cpu=args.cpu, memory=args.memory, pci_list=None, start=True)
else:
# No hardware configuration needed, just start the VM
logger.info(f"No hardware configuration specified, starting VM {args.vm_name}")
run_qcow2_modify_hardware_config(args.profile, args.vm_name, cpu=None, memory=None, pci_list=None, start=True)
except KeyboardInterrupt:
logger.error("so-salt-cloud: Operation cancelled by user.")

View File

@@ -8,9 +8,12 @@
{% from 'vars/globals.map.jinja' import GLOBALS %}
{% from "pcap/config.map.jinja" import PCAPMERGED %}
{% from 'bpf/pcap.map.jinja' import PCAPBPF, PCAP_BPF_STATUS, PCAP_BPF_CALC, STENO_BPF_COMPILED %}
{% from 'bpf/pcap.map.jinja' import PCAPBPF %}
{% set BPF_COMPILED = "" %}
# PCAP Section
stenographergroup:
group.present:
- name: stenographer
@@ -37,12 +40,18 @@ pcap_sbin:
- group: 939
- file_mode: 755
{% if PCAPBPF and not PCAP_BPF_STATUS %}
stenoPCAPbpfcompilationfailure:
{% if PCAPBPF %}
{% set BPF_CALC = salt['cmd.script']('salt://common/tools/sbin/so-bpf-compile', GLOBALS.sensor.interface + ' ' + PCAPBPF|join(" "),cwd='/root') %}
{% if BPF_CALC['stderr'] == "" %}
{% set BPF_COMPILED = ",\\\"--filter=" + BPF_CALC['stdout'] + "\\\"" %}
{% else %}
bpfcompilationfailure:
test.configurable_test_state:
- changes: False
- result: False
- comment: "BPF Syntax Error - Discarding Specified BPF. Error: {{ PCAP_BPF_CALC['stderr'] }}"
- comment: "BPF Compilation Failed - Discarding Specified BPF"
{% endif %}
{% endif %}
stenoconf:
@@ -55,7 +64,7 @@ stenoconf:
- template: jinja
- defaults:
PCAPMERGED: {{ PCAPMERGED }}
STENO_BPF_COMPILED: "{{ STENO_BPF_COMPILED }}"
BPF_COMPILED: "{{ BPF_COMPILED }}"
stenoca:
file.directory:

View File

@@ -6,6 +6,6 @@
, "Interface": "{{ pillar.sensor.interface }}"
, "Port": 1234
, "Host": "127.0.0.1"
, "Flags": ["-v", "--blocks={{ PCAPMERGED.config.blocks }}", "--preallocate_file_mb={{ PCAPMERGED.config.preallocate_file_mb }}", "--aiops={{ PCAPMERGED.config.aiops }}", "--uid=stenographer", "--gid=stenographer"{{ STENO_BPF_COMPILED }}]
, "Flags": ["-v", "--blocks={{ PCAPMERGED.config.blocks }}", "--preallocate_file_mb={{ PCAPMERGED.config.preallocate_file_mb }}", "--aiops={{ PCAPMERGED.config.aiops }}", "--uid=stenographer", "--gid=stenographer"{{ BPF_COMPILED }}]
, "CertPath": "/etc/stenographer/certs"
}

View File

@@ -7,7 +7,7 @@ pcap:
description: By default, Stenographer limits the number of files in the pcap directory to 30000 to avoid limitations with the ext3 filesystem. However, if you're using the ext4 or xfs filesystems, then it is safe to increase this value. So if you have a large amount of storage and find that you only have 3 weeks worth of PCAP on disk while still having plenty of free space, then you may want to increase this default setting.
helpLink: stenographer.html
diskfreepercentage:
description: Stenographer will purge old PCAP on a regular basis to keep the disk free percentage at this level. If you have a distributed deployment with dedicated Sensor nodes, then the default value of 10 should be reasonable since Stenographer should be the main consumer of disk space in the /nsm partition. However, if you have systems that run both Stenographer and Elasticsearch at the same time (like eval and standalone installations), then youll want to make sure that this value is no lower than 21 so that you avoid Elasticsearch hitting its watermark setting at 80% disk usage. If you have an older standalone installation, then you may need to manually change this value to 21.
description: Stenographer will purge old PCAP on a regular basis to keep the disk free percentage at this level. If you have a distributed deployment with dedicated forward nodes, then the default value of 10 should be reasonable since Stenographer should be the main consumer of disk space in the /nsm partition. However, if you have systems that run both Stenographer and Elasticsearch at the same time (like eval and standalone installations), then youll want to make sure that this value is no lower than 21 so that you avoid Elasticsearch hitting its watermark setting at 80% disk usage. If you have an older standalone installation, then you may need to manually change this value to 21.
helpLink: stenographer.html
blocks:
description: The number of 1MB packet blocks used by Stenographer and AF_PACKET to store packets in memory, per thread. You shouldn't need to change this.

View File

@@ -5,7 +5,6 @@
{% from 'allowed_states.map.jinja' import allowed_states %}
{% if sls.split('.')[0] in allowed_states %}
{% from 'vars/globals.map.jinja' import GLOBALS %}
{% from 'docker/docker.map.jinja' import DOCKER %}
include:
@@ -58,17 +57,6 @@ so-dockerregistry:
- x509: registry_crt
- x509: registry_key
wait_for_so-dockerregistry:
http.wait_for_successful_query:
- name: 'https://{{ GLOBALS.registry_host }}:5000/v2/'
- ssl: True
- verify_ssl: False
- status: 200
- wait_for: 120
- request_interval: 5
- require:
- docker_container: so-dockerregistry
delete_so-dockerregistry_so-status.disabled:
file.uncomment:
- name: /opt/so/conf/so-status/so-status.conf

View File

@@ -14,7 +14,7 @@ sool9_{{host}}:
private_key: /etc/ssh/auth_keys/soqemussh/id_ecdsa
sudo: True
deploy_command: sh /tmp/.saltcloud-*/deploy.sh
script_args: -r -F -x python3 stable {{ SALTVERSION }}
script_args: -r -F -x python3 stable 3006.9
minion:
master: {{ grains.host }}
master_port: 4506

View File

@@ -13,7 +13,6 @@
{% if '.'.join(sls.split('.')[:2]) in allowed_states %}
{% if 'vrt' in salt['pillar.get']('features', []) %}
{% set HYPERVISORS = salt['pillar.get']('hypervisor:nodes', {} ) %}
{% from 'salt/map.jinja' import SALTVERSION %}
{% if HYPERVISORS %}
cloud_providers:
@@ -21,7 +20,7 @@ cloud_providers:
- name: /etc/salt/cloud.providers.d/libvirt.conf
- source: salt://salt/cloud/cloud.providers.d/libvirt.conf.jinja
- defaults:
HYPERVISORS: {{ HYPERVISORS }}
HYPERVISORS: {{HYPERVISORS}}
- template: jinja
- makedirs: True
@@ -30,17 +29,11 @@ cloud_profiles:
- name: /etc/salt/cloud.profiles.d/socloud.conf
- source: salt://salt/cloud/cloud.profiles.d/socloud.conf.jinja
- defaults:
HYPERVISORS: {{ HYPERVISORS }}
HYPERVISORS: {{HYPERVISORS}}
MANAGERHOSTNAME: {{ grains.host }}
MANAGERIP: {{ pillar.host.mainip }}
SALTVERSION: {{ SALTVERSION }}
- template: jinja
- makedirs: True
{% else %}
no_hypervisors_configured:
test.succeed_without_changes:
- name: no_hypervisors_configured
- comment: No hypervisors are configured
{% endif %}
{% else %}

View File

@@ -117,7 +117,7 @@ Exit Codes:
4: VM provisioning failure (so-salt-cloud execution failed)
Logging:
Log files are written to /opt/so/log/salt/engines/virtual_node_manager
Log files are written to /opt/so/log/salt/engines/virtual_node_manager.log
Comprehensive logging includes:
- Hardware validation details
- PCI ID conversion process
@@ -138,56 +138,29 @@ import pwd
import grp
import salt.config
import salt.runner
import salt.client
from typing import Dict, List, Optional, Tuple, Any
from datetime import datetime, timedelta
from threading import Lock
# Initialize Salt runner and local client once
# Get socore uid/gid
SOCORE_UID = pwd.getpwnam('socore').pw_uid
SOCORE_GID = grp.getgrnam('socore').gr_gid
# Initialize Salt runner once
opts = salt.config.master_config('/etc/salt/master')
opts['output'] = 'json'
runner = salt.runner.RunnerClient(opts)
local = salt.client.LocalClient()
# Get socore uid/gid for file ownership
SOCORE_UID = pwd.getpwnam('socore').pw_uid
SOCORE_GID = grp.getgrnam('socore').gr_gid
# Configure logging
log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)
# Prevent propagation to parent loggers to avoid duplicate log entries
log.propagate = False
# Add file handler for dedicated log file
log_dir = '/opt/so/log/salt'
log_file = os.path.join(log_dir, 'virtual_node_manager')
# Create log directory if it doesn't exist
os.makedirs(log_dir, exist_ok=True)
# Create file handler
file_handler = logging.FileHandler(log_file)
file_handler.setLevel(logging.DEBUG)
# Create formatter
formatter = logging.Formatter(
'%(asctime)s [%(name)s:%(lineno)d][%(levelname)-8s][%(process)d] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
file_handler.setFormatter(formatter)
# Add handler to logger
log.addHandler(file_handler)
# Constants
DEFAULT_INTERVAL = 30
DEFAULT_BASE_PATH = '/opt/so/saltstack/local/salt/hypervisor/hosts'
VALID_ROLES = ['sensor', 'searchnode', 'idh', 'receiver', 'heavynode', 'fleet']
LICENSE_PATH = '/opt/so/saltstack/local/pillar/soc/license.sls'
DEFAULTS_PATH = '/opt/so/saltstack/default/salt/hypervisor/defaults.yaml'
HYPERVISOR_PILLAR_PATH = '/opt/so/saltstack/local/pillar/hypervisor/soc_hypervisor.sls'
# Define the retention period for destroyed VMs (in hours)
DESTROYED_VM_RETENTION_HOURS = 48
@@ -229,39 +202,6 @@ def write_json_file(file_path: str, data: Any) -> None:
except Exception as e:
log.error("Failed to write JSON file %s: %s", file_path, str(e))
raise
def remove_vm_from_vms_file(vms_file_path: str, vm_hostname: str, vm_role: str) -> bool:
"""
Remove a VM entry from the hypervisorVMs file.
Args:
vms_file_path: Path to the hypervisorVMs file
vm_hostname: Hostname of the VM to remove (without role suffix)
vm_role: Role of the VM
Returns:
bool: True if VM was removed, False otherwise
"""
try:
# Read current VMs
vms = read_json_file(vms_file_path)
# Find and remove the VM entry
original_count = len(vms)
vms = [vm for vm in vms if not (vm.get('hostname') == vm_hostname and vm.get('role') == vm_role)]
if len(vms) < original_count:
# VM was found and removed, write back to file
write_json_file(vms_file_path, vms)
log.info("Removed VM %s_%s from %s", vm_hostname, vm_role, vms_file_path)
return True
else:
log.warning("VM %s_%s not found in %s", vm_hostname, vm_role, vms_file_path)
return False
except Exception as e:
log.error("Failed to remove VM %s_%s from %s: %s", vm_hostname, vm_role, vms_file_path, str(e))
return False
def read_yaml_file(file_path: str) -> dict:
"""Read and parse a YAML file."""
@@ -331,7 +271,7 @@ def parse_hardware_indices(hw_value: Any) -> List[int]:
return indices
def get_hypervisor_model(hypervisor: str) -> str:
"""Get sosmodel or byodmodel from hypervisor grains."""
"""Get sosmodel from hypervisor grains."""
try:
# Get cached grains using Salt runner
grains = runner.cmd(
@@ -343,9 +283,9 @@ def get_hypervisor_model(hypervisor: str) -> str:
# Get the first minion ID that matches our hypervisor
minion_id = next(iter(grains.keys()))
model = grains[minion_id].get('sosmodel', grains[minion_id].get('byodmodel', ''))
model = grains[minion_id].get('sosmodel')
if not model:
raise ValueError(f"No sosmodel or byodmodel grain found for hypervisor {hypervisor}")
raise ValueError(f"No sosmodel grain found for hypervisor {hypervisor}")
log.debug("Found model %s for hypervisor %s", model, hypervisor)
return model
@@ -355,48 +295,16 @@ def get_hypervisor_model(hypervisor: str) -> str:
raise
def load_hardware_defaults(model: str) -> dict:
"""Load hardware configuration from defaults.yaml and optionally override with pillar configuration."""
config = None
config_source = None
"""Load hardware configuration from defaults.yaml."""
try:
# First, try to load from defaults.yaml
log.debug("Checking for model %s in %s", model, DEFAULTS_PATH)
defaults = read_yaml_file(DEFAULTS_PATH)
if not defaults or 'hypervisor' not in defaults:
raise ValueError("Invalid defaults.yaml structure")
if 'model' not in defaults['hypervisor']:
raise ValueError("No model configurations found in defaults.yaml")
# Check if model exists in defaults
if model in defaults['hypervisor']['model']:
config = defaults['hypervisor']['model'][model]
config_source = DEFAULTS_PATH
log.debug("Found model %s in %s", model, DEFAULTS_PATH)
# Then, try to load from pillar file (if it exists)
try:
log.debug("Checking for model %s in %s", model, HYPERVISOR_PILLAR_PATH)
pillar_config = read_yaml_file(HYPERVISOR_PILLAR_PATH)
if pillar_config and 'hypervisor' in pillar_config:
if 'model' in pillar_config['hypervisor']:
if model in pillar_config['hypervisor']['model']:
# Override with pillar configuration
config = pillar_config['hypervisor']['model'][model]
config_source = HYPERVISOR_PILLAR_PATH
log.debug("Found model %s in %s (overriding defaults)", model, HYPERVISOR_PILLAR_PATH)
except FileNotFoundError:
log.debug("Pillar file %s not found, using defaults only", HYPERVISOR_PILLAR_PATH)
except Exception as e:
log.warning("Failed to read pillar file %s: %s (using defaults)", HYPERVISOR_PILLAR_PATH, str(e))
# If model was not found in either file, raise an error
if config is None:
raise ValueError(f"Model {model} not found in {DEFAULTS_PATH} or {HYPERVISOR_PILLAR_PATH}")
log.debug("Using hardware configuration for model %s from %s", model, config_source)
return config
if model not in defaults['hypervisor']['model']:
raise ValueError(f"Model {model} not found in defaults.yaml")
return defaults['hypervisor']['model'][model]
except Exception as e:
log.error("Failed to load hardware defaults: %s", str(e))
raise
@@ -617,13 +525,6 @@ def mark_vm_failed(vm_file: str, error_code: int, message: str) -> None:
# Remove the original file since we'll create an error file
os.remove(vm_file)
# Clear hardware resource claims so failed VMs don't consume resources
# Keep nsm_size for reference but clear cpu, memory, sfp, copper
config.pop('cpu', None)
config.pop('memory', None)
config.pop('sfp', None)
config.pop('copper', None)
# Create error file
error_file = f"{vm_file}.error"
data = {
@@ -652,16 +553,8 @@ def mark_invalid_hardware(hypervisor_path: str, vm_name: str, config: dict, erro
# Join all messages with proper sentence structure
full_message = "Hardware validation failure: " + " ".join(error_messages)
# Clear hardware resource claims so failed VMs don't consume resources
# Keep nsm_size for reference but clear cpu, memory, sfp, copper
config_copy = config.copy()
config_copy.pop('cpu', None)
config_copy.pop('memory', None)
config_copy.pop('sfp', None)
config_copy.pop('copper', None)
data = {
'config': config_copy,
'config': config,
'status': 'error',
'timestamp': datetime.now().isoformat(),
'error_details': {
@@ -708,62 +601,6 @@ def validate_vrt_license() -> bool:
log.error("Error reading license file: %s", str(e))
return False
def check_hypervisor_disk_space(hypervisor: str, size_gb: int) -> Tuple[bool, Optional[str]]:
"""
Check if hypervisor has sufficient disk space for volume creation.
Args:
hypervisor: Hypervisor hostname
size_gb: Required size in GB
Returns:
Tuple of (has_space, error_message)
"""
try:
# Get hypervisor minion ID
hypervisor_minion = f"{hypervisor}_hypervisor"
# Check disk space on /nsm/libvirt/volumes using LocalClient
result = local.cmd(
hypervisor_minion,
'cmd.run',
["df -BG /nsm/libvirt/volumes | tail -1 | awk '{print $4}' | sed 's/G//'"],
kwarg={'python_shell': True}
)
if not result or hypervisor_minion not in result:
log.error("Failed to check disk space on hypervisor %s", hypervisor)
return False, "Failed to check disk space on hypervisor"
available_gb_str = result[hypervisor_minion].strip()
if not available_gb_str:
log.error("Empty disk space response from hypervisor %s", hypervisor)
return False, "Failed to get disk space information"
try:
available_gb = float(available_gb_str)
except ValueError:
log.error("Invalid disk space value from hypervisor %s: %s", hypervisor, available_gb_str)
return False, f"Invalid disk space value: {available_gb_str}"
# Add 10% buffer for filesystem overhead
required_gb = size_gb * 1.1
log.debug("Hypervisor %s disk space check: Available=%.2fGB, Required=%.2fGB",
hypervisor, available_gb, required_gb)
if available_gb < required_gb:
error_msg = f"Insufficient disk space on hypervisor {hypervisor}. Available: {available_gb:.2f}GB, Required: {required_gb:.2f}GB (including 10% overhead)"
log.error(error_msg)
return False, error_msg
log.info("Hypervisor %s has sufficient disk space for %dGB volume", hypervisor, size_gb)
return True, None
except Exception as e:
log.error("Error checking disk space on hypervisor %s: %s", hypervisor, str(e))
return False, f"Error checking disk space: {str(e)}"
def process_vm_creation(hypervisor_path: str, vm_config: dict) -> None:
"""
Process a single VM creation request.
@@ -796,62 +633,6 @@ def process_vm_creation(hypervisor_path: str, vm_config: dict) -> None:
except subprocess.CalledProcessError as e:
logger.error(f"Failed to emit success status event: {e}")
# Validate nsm_size if present
if 'nsm_size' in vm_config:
try:
size = int(vm_config['nsm_size'])
if size <= 0:
log.error("VM: %s - nsm_size must be a positive integer, got: %d", vm_name, size)
mark_invalid_hardware(hypervisor_path, vm_name, vm_config,
{'nsm_size': 'Invalid nsm_size: must be positive integer'})
return
if size > 10000: # 10TB reasonable maximum
log.error("VM: %s - nsm_size %dGB exceeds reasonable maximum (10000GB)", vm_name, size)
mark_invalid_hardware(hypervisor_path, vm_name, vm_config,
{'nsm_size': f'Invalid nsm_size: {size}GB exceeds maximum (10000GB)'})
return
log.debug("VM: %s - nsm_size validated: %dGB", vm_name, size)
except (ValueError, TypeError) as e:
log.error("VM: %s - nsm_size must be a valid integer, got: %s", vm_name, vm_config.get('nsm_size'))
mark_invalid_hardware(hypervisor_path, vm_name, vm_config,
{'nsm_size': 'Invalid nsm_size: must be valid integer'})
return
# Check for conflicting storage configurations
has_disk = 'disk' in vm_config and vm_config['disk']
has_nsm_size = 'nsm_size' in vm_config and vm_config['nsm_size']
if has_disk and has_nsm_size:
log.warning("VM: %s - Both disk and nsm_size specified. disk takes precedence, nsm_size will be ignored.",
vm_name)
# Check disk space BEFORE creating VM if nsm_size is specified
if has_nsm_size and not has_disk:
size_gb = int(vm_config['nsm_size'])
has_space, space_error = check_hypervisor_disk_space(hypervisor, size_gb)
if not has_space:
log.error("VM: %s - %s", vm_name, space_error)
# Send Hypervisor NSM Disk Full status event
try:
subprocess.run([
'so-salt-emit-vm-deployment-status-event',
'-v', vm_name,
'-H', hypervisor,
'-s', 'Hypervisor NSM Disk Full'
], check=True)
except subprocess.CalledProcessError as e:
log.error("Failed to emit volume create failed event for %s: %s", vm_name, str(e))
mark_invalid_hardware(
hypervisor_path,
vm_name,
vm_config,
{'disk_space': f"Insufficient disk space for {size_gb}GB volume: {space_error}"}
)
return
log.debug("VM: %s - Hypervisor has sufficient space for %dGB volume", vm_name, size_gb)
# Initial hardware validation against model
is_valid, errors = validate_hardware_request(model_config, vm_config)
if not is_valid:
@@ -888,11 +669,6 @@ def process_vm_creation(hypervisor_path: str, vm_config: dict) -> None:
memory_mib = int(vm_config['memory']) * 1024
cmd.extend(['-m', str(memory_mib)])
# Add nsm_size if specified and disk is not specified
if 'nsm_size' in vm_config and vm_config['nsm_size'] and not ('disk' in vm_config and vm_config['disk']):
cmd.extend(['--nsm-size', str(vm_config['nsm_size'])])
log.debug("VM: %s - Adding nsm_size parameter: %s", vm_name, vm_config['nsm_size'])
# Add PCI devices
for hw_type in ['disk', 'copper', 'sfp']:
if hw_type in vm_config and vm_config[hw_type]:
@@ -1124,21 +900,12 @@ def process_hypervisor(hypervisor_path: str) -> None:
if not nodes_config:
log.debug("Empty VMs configuration in %s", vms_file)
# Get existing VMs and track failed VMs separately
# Get existing VMs
existing_vms = set()
failed_vms = set() # VMs with .error files
for file_path in glob.glob(os.path.join(hypervisor_path, '*_*')):
basename = os.path.basename(file_path)
# Skip status files
if basename.endswith('.status'):
continue
# Track VMs with .error files separately
if basename.endswith('.error'):
vm_name = basename[:-6] # Remove '.error' suffix
failed_vms.add(vm_name)
existing_vms.add(vm_name) # Also add to existing to prevent recreation
log.debug(f"Found failed VM with .error file: {vm_name}")
else:
# Skip error and status files
if not basename.endswith('.error') and not basename.endswith('.status'):
existing_vms.add(basename)
# Process new VMs
@@ -1155,37 +922,12 @@ def process_hypervisor(hypervisor_path: str) -> None:
# process_vm_creation handles its own locking
process_vm_creation(hypervisor_path, vm_config)
# Process VM deletions (but skip failed VMs that only have .error files)
# Process VM deletions
vms_to_delete = existing_vms - configured_vms
log.debug(f"Existing VMs: {existing_vms}")
log.debug(f"Configured VMs: {configured_vms}")
log.debug(f"Failed VMs: {failed_vms}")
log.debug(f"VMs to delete: {vms_to_delete}")
for vm_name in vms_to_delete:
# Skip deletion if VM only has .error file (no actual VM to delete)
if vm_name in failed_vms:
error_file = os.path.join(hypervisor_path, f"{vm_name}.error")
base_file = os.path.join(hypervisor_path, vm_name)
# Only skip if there's no base file (VM never successfully created)
if not os.path.exists(base_file):
log.info(f"Skipping deletion of failed VM {vm_name} (VM never successfully created)")
# Clean up the .error and .status files since VM is no longer configured
if os.path.exists(error_file):
os.remove(error_file)
log.info(f"Removed .error file for unconfigured VM: {vm_name}")
status_file = os.path.join(hypervisor_path, f"{vm_name}.status")
if os.path.exists(status_file):
os.remove(status_file)
log.info(f"Removed .status file for unconfigured VM: {vm_name}")
# Trigger hypervisor annotation update to reflect the removal
try:
log.info(f"Triggering hypervisor annotation update after removing failed VM: {vm_name}")
runner.cmd('state.orch', ['orch.dyanno_hypervisor'])
except Exception as e:
log.error(f"Failed to trigger hypervisor annotation update for {vm_name}: {str(e)}")
continue
log.info(f"Initiating deletion process for VM: {vm_name}")
process_vm_deletion(hypervisor_path, vm_name)

View File

@@ -4,10 +4,7 @@
Elastic License 2.0. #}
{% set role = salt['grains.get']('role', '') %}
{# We are using usebr0 mostly for setup of the so-managerhype node and controlling when we use br0 vs the physical interface #}
{% set usebr0 = salt['pillar.get']('usebr0', True) %}
{% if role in ['so-hypervisor','so-managerhype'] and usebr0 %}
{% if role in ['so-hypervisor','so-managerhype'] and salt['network.ip_addrs']('br0')|length > 0 %}
{% set interface = 'br0' %}
{% else %}
{% set interface = pillar.host.mainint %}

View File

@@ -1,4 +1,4 @@
# version cannot be used elsewhere in this pillar as soup is grepping for it to determine if Salt needs to be patched
salt:
master:
version: '3006.16'
version: '3006.9'

View File

@@ -3,7 +3,7 @@
# https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0.
# this state was separated from salt.minion state since it is called during setup
# this state was seperated from salt.minion state since it is called during setup
# GLOBALS are imported in the salt.minion state and that is not available at that point in setup
# this state is included in the salt.minion state

View File

@@ -1,5 +1,5 @@
# version cannot be used elsewhere in this pillar as soup is grepping for it to determine if Salt needs to be patched
salt:
minion:
version: '3006.16'
version: '3006.9'
check_threshold: 3600 # in seconds, threshold used for so-salt-minion-check. any value less than 600 seconds may cause a lot of salt-minion restarts since the job to touch the file occurs every 5-8 minutes by default

View File

@@ -1,22 +1,18 @@
# 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 'vars/globals.map.jinja' import GLOBALS %}
{% from 'salt/map.jinja' import UPGRADECOMMAND with context %}
{% from 'salt/map.jinja' import SALTVERSION %}
{% from 'salt/map.jinja' import INSTALLEDSALTVERSION %}
{% from 'salt/map.jinja' import SALTPACKAGES %}
{% from 'salt/map.jinja' import SYSTEMD_UNIT_FILE %}
{% import_yaml 'salt/minion.defaults.yaml' as SALTMINION %}
include:
- salt.python_modules
- salt.patch.x509_v2
- salt
- systemd.reload
- repo.client
- salt.mine_functions
- salt.minion.service_file
{% if GLOBALS.role in GLOBALS.manager_roles %}
- ca
{% endif %}
@@ -98,6 +94,17 @@ enable_startup_states:
- regex: '^startup_states: highstate$'
- unless: pgrep so-setup
# prior to 2.4.30 this managed file would restart the salt-minion service when updated
# since this file is currently only adding a delay service start
# it is not required to restart the service
salt_minion_service_unit_file:
file.managed:
- name: {{ SYSTEMD_UNIT_FILE }}
- source: salt://salt/service/salt-minion.service.jinja
- template: jinja
- onchanges_in:
- module: systemd_reload
{% endif %}
# this has to be outside the if statement above since there are <requisite>_in calls to this state

View File

@@ -1,26 +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 'salt/map.jinja' import SALTVERSION %}
{% from 'salt/map.jinja' import INSTALLEDSALTVERSION %}
{% from 'salt/map.jinja' import SYSTEMD_UNIT_FILE %}
include:
- systemd.reload
{% if INSTALLEDSALTVERSION|string == SALTVERSION|string %}
# prior to 2.4.30 this managed file would restart the salt-minion service when updated
# since this file is currently only adding a delay service start
# it is not required to restart the service
salt_minion_service_unit_file:
file.managed:
- name: {{ SYSTEMD_UNIT_FILE }}
- source: salt://salt/service/salt-minion.service.jinja
- template: jinja
- onchanges_in:
- module: systemd_reload
{% endif %}

View File

@@ -26,7 +26,7 @@
#======================================================================================================================
set -o nounset # Treat unset variables as an error
__ScriptVersion="2025.09.03"
__ScriptVersion="2025.02.24"
__ScriptName="bootstrap-salt.sh"
__ScriptFullName="$0"
@@ -48,7 +48,6 @@ __ScriptArgs="$*"
# * BS_GENTOO_USE_BINHOST: If 1 add `--getbinpkg` to gentoo's emerge
# * BS_SALT_MASTER_ADDRESS: The IP or DNS name of the salt-master the minion should connect to
# * BS_SALT_GIT_CHECKOUT_DIR: The directory where to clone Salt on git installations
# * BS_TMP_DIR: The directory to use for executing the installation (defaults to /tmp)
#======================================================================================================================
@@ -172,12 +171,12 @@ __check_config_dir() {
case "$CC_DIR_NAME" in
http://*|https://*)
__fetch_url "${_TMP_DIR}/${CC_DIR_BASE}" "${CC_DIR_NAME}"
CC_DIR_NAME="${_TMP_DIR}/${CC_DIR_BASE}"
__fetch_url "/tmp/${CC_DIR_BASE}" "${CC_DIR_NAME}"
CC_DIR_NAME="/tmp/${CC_DIR_BASE}"
;;
ftp://*)
__fetch_url "${_TMP_DIR}/${CC_DIR_BASE}" "${CC_DIR_NAME}"
CC_DIR_NAME="${_TMP_DIR}/${CC_DIR_BASE}"
__fetch_url "/tmp/${CC_DIR_BASE}" "${CC_DIR_NAME}"
CC_DIR_NAME="/tmp/${CC_DIR_BASE}"
;;
*://*)
echoerror "Unsupported URI scheme for $CC_DIR_NAME"
@@ -195,22 +194,22 @@ __check_config_dir() {
case "$CC_DIR_NAME" in
*.tgz|*.tar.gz)
tar -zxf "${CC_DIR_NAME}" -C ${_TMP_DIR}
tar -zxf "${CC_DIR_NAME}" -C /tmp
CC_DIR_BASE=$(basename "${CC_DIR_BASE}" ".tgz")
CC_DIR_BASE=$(basename "${CC_DIR_BASE}" ".tar.gz")
CC_DIR_NAME="${_TMP_DIR}/${CC_DIR_BASE}"
CC_DIR_NAME="/tmp/${CC_DIR_BASE}"
;;
*.tbz|*.tar.bz2)
tar -xjf "${CC_DIR_NAME}" -C ${_TMP_DIR}
tar -xjf "${CC_DIR_NAME}" -C /tmp
CC_DIR_BASE=$(basename "${CC_DIR_BASE}" ".tbz")
CC_DIR_BASE=$(basename "${CC_DIR_BASE}" ".tar.bz2")
CC_DIR_NAME="${_TMP_DIR}/${CC_DIR_BASE}"
CC_DIR_NAME="/tmp/${CC_DIR_BASE}"
;;
*.txz|*.tar.xz)
tar -xJf "${CC_DIR_NAME}" -C ${_TMP_DIR}
tar -xJf "${CC_DIR_NAME}" -C /tmp
CC_DIR_BASE=$(basename "${CC_DIR_BASE}" ".txz")
CC_DIR_BASE=$(basename "${CC_DIR_BASE}" ".tar.xz")
CC_DIR_NAME="${_TMP_DIR}/${CC_DIR_BASE}"
CC_DIR_NAME="/tmp/${CC_DIR_BASE}"
;;
esac
@@ -246,7 +245,6 @@ __check_unparsed_options() {
#----------------------------------------------------------------------------------------------------------------------
_KEEP_TEMP_FILES=${BS_KEEP_TEMP_FILES:-$BS_FALSE}
_TEMP_CONFIG_DIR="null"
_TMP_DIR=${BS_TMP_DIR:-"/tmp"}
_SALTSTACK_REPO_URL="https://github.com/saltstack/salt.git"
_SALT_REPO_URL=${_SALTSTACK_REPO_URL}
_TEMP_KEYS_DIR="null"
@@ -283,7 +281,7 @@ _SIMPLIFY_VERSION=$BS_TRUE
_LIBCLOUD_MIN_VERSION="0.14.0"
_EXTRA_PACKAGES=""
_HTTP_PROXY=""
_SALT_GIT_CHECKOUT_DIR=${BS_SALT_GIT_CHECKOUT_DIR:-${_TMP_DIR}/git/salt}
_SALT_GIT_CHECKOUT_DIR=${BS_SALT_GIT_CHECKOUT_DIR:-/tmp/git/salt}
_NO_DEPS=$BS_FALSE
_FORCE_SHALLOW_CLONE=$BS_FALSE
_DISABLE_SSL=$BS_FALSE
@@ -369,7 +367,7 @@ __usage() {
also be specified. Salt installation will be ommitted, but some of the
dependencies could be installed to write configuration with -j or -J.
-d Disables checking if Salt services are enabled to start on system boot.
You can also do this by touching ${BS_TMP_DIR}/disable_salt_checks on the target
You can also do this by touching /tmp/disable_salt_checks on the target
host. Default: \${BS_FALSE}
-D Show debug output
-f Force shallow cloning for git installations.
@@ -426,9 +424,6 @@ __usage() {
-r Disable all repository configuration performed by this script. This
option assumes all necessary repository configuration is already present
on the system.
-T If set this overrides the use of /tmp for script execution. This is
to allow for systems in which noexec is applied to temp filesystem mounts
for security reasons
-U If set, fully upgrade the system prior to bootstrapping Salt
-v Display script version
-V Install Salt into virtualenv
@@ -441,7 +436,7 @@ __usage() {
EOT
} # ---------- end of function __usage ----------
while getopts ':hvnDc:g:Gx:k:s:MSWNXCPFUKIA:i:Lp:dH:bflV:J:j:rR:T:aqQ' opt
while getopts ':hvnDc:g:Gx:k:s:MSWNXCPFUKIA:i:Lp:dH:bflV:J:j:rR:aqQ' opt
do
case "${opt}" in
@@ -483,7 +478,6 @@ do
a ) _PIP_ALL=$BS_TRUE ;;
r ) _DISABLE_REPOS=$BS_TRUE ;;
R ) _CUSTOM_REPO_URL=$OPTARG ;;
T ) _TMP_DIR="$OPTARG" ;;
J ) _CUSTOM_MASTER_CONFIG=$OPTARG ;;
j ) _CUSTOM_MINION_CONFIG=$OPTARG ;;
q ) _QUIET_GIT_INSTALLATION=$BS_TRUE ;;
@@ -501,10 +495,10 @@ done
shift $((OPTIND-1))
# Define our logging file and pipe paths
LOGFILE="${_TMP_DIR}/$( echo "$__ScriptName" | sed s/.sh/.log/g )"
LOGPIPE="${_TMP_DIR}/$( echo "$__ScriptName" | sed s/.sh/.logpipe/g )"
LOGFILE="/tmp/$( echo "$__ScriptName" | sed s/.sh/.log/g )"
LOGPIPE="/tmp/$( echo "$__ScriptName" | sed s/.sh/.logpipe/g )"
# Ensure no residual pipe exists
rm -f "$LOGPIPE" 2>/dev/null
rm "$LOGPIPE" 2>/dev/null
# Create our logging pipe
# On FreeBSD we have to use mkfifo instead of mknod
@@ -540,7 +534,7 @@ exec 2>"$LOGPIPE"
# 14 SIGALRM
# 15 SIGTERM
#----------------------------------------------------------------------------------------------------------------------
APT_ERR=$(mktemp ${_TMP_DIR}/apt_error.XXXXXX)
APT_ERR=$(mktemp /tmp/apt_error.XXXXXX)
__exit_cleanup() {
EXIT_CODE=$?
@@ -933,11 +927,6 @@ if [ -d "${_VIRTUALENV_DIR}" ]; then
exit 1
fi
# Make sure the designated temp directory exists
if [ ! -d "${_TMP_DIR}" ]; then
mkdir -p "${_TMP_DIR}"
fi
#--- FUNCTION -------------------------------------------------------------------------------------------------------
# NAME: __fetch_url
# DESCRIPTION: Retrieves a URL and writes it to a given path
@@ -1952,6 +1941,11 @@ __wait_for_apt(){
# Timeout set at 15 minutes
WAIT_TIMEOUT=900
## see if sync'ing the clocks helps
if [ -f /usr/sbin/hwclock ]; then
/usr/sbin/hwclock -s
fi
# Run our passed in apt command
"${@}" 2>"$APT_ERR"
APT_RETURN=$?
@@ -2002,14 +1996,14 @@ __apt_get_upgrade_noinput() {
#----------------------------------------------------------------------------------------------------------------------
__temp_gpg_pub() {
if __check_command_exists mktemp; then
tempfile="$(mktemp ${_TMP_DIR}/salt-gpg-XXXXXXXX.pub 2>/dev/null)"
tempfile="$(mktemp /tmp/salt-gpg-XXXXXXXX.pub 2>/dev/null)"
if [ -z "$tempfile" ]; then
echoerror "Failed to create temporary file in ${_TMP_DIR}"
echoerror "Failed to create temporary file in /tmp"
return 1
fi
else
tempfile="${_TMP_DIR}/salt-gpg-$$.pub"
tempfile="/tmp/salt-gpg-$$.pub"
fi
echo $tempfile
@@ -2049,7 +2043,7 @@ __rpm_import_gpg() {
__fetch_url "$tempfile" "$url" || return 1
# At least on CentOS 8, a missing newline at the end causes:
# error: ${_TMP_DIR}/salt-gpg-n1gKUb1u.pub: key 1 not an armored public key.
# error: /tmp/salt-gpg-n1gKUb1u.pub: key 1 not an armored public key.
# shellcheck disable=SC1003,SC2086
sed -i -e '$a\' $tempfile
@@ -2115,7 +2109,7 @@ __git_clone_and_checkout() {
fi
__SALT_GIT_CHECKOUT_PARENT_DIR=$(dirname "${_SALT_GIT_CHECKOUT_DIR}" 2>/dev/null)
__SALT_GIT_CHECKOUT_PARENT_DIR="${__SALT_GIT_CHECKOUT_PARENT_DIR:-${_TMP_DIR}/git}"
__SALT_GIT_CHECKOUT_PARENT_DIR="${__SALT_GIT_CHECKOUT_PARENT_DIR:-/tmp/git}"
__SALT_CHECKOUT_REPONAME="$(basename "${_SALT_GIT_CHECKOUT_DIR}" 2>/dev/null)"
__SALT_CHECKOUT_REPONAME="${__SALT_CHECKOUT_REPONAME:-salt}"
[ -d "${__SALT_GIT_CHECKOUT_PARENT_DIR}" ] || mkdir "${__SALT_GIT_CHECKOUT_PARENT_DIR}"
@@ -2396,14 +2390,14 @@ __overwriteconfig() {
# Make a tempfile to dump any python errors into.
if __check_command_exists mktemp; then
tempfile="$(mktemp ${_TMP_DIR}/salt-config-XXXXXXXX 2>/dev/null)"
tempfile="$(mktemp /tmp/salt-config-XXXXXXXX 2>/dev/null)"
if [ -z "$tempfile" ]; then
echoerror "Failed to create temporary file in ${_TMP_DIR}"
echoerror "Failed to create temporary file in /tmp"
return 1
fi
else
tempfile="${_TMP_DIR}/salt-config-$$"
tempfile="/tmp/salt-config-$$"
fi
if [ -n "$_PY_EXE" ]; then
@@ -2766,8 +2760,8 @@ __install_salt_from_repo() {
echoinfo "Installing salt using ${_py_exe}, $(${_py_exe} --version)"
cd "${_SALT_GIT_CHECKOUT_DIR}" || return 1
mkdir -p ${_TMP_DIR}/git/deps
echodebug "Created directory ${_TMP_DIR}/git/deps"
mkdir -p /tmp/git/deps
echodebug "Created directory /tmp/git/deps"
if [ ${DISTRO_NAME_L} = "ubuntu" ] && [ "$DISTRO_MAJOR_VERSION" -eq 22 ]; then
echodebug "Ubuntu 22.04 has problem with base.txt requirements file, not parsing sys_platform == 'win32', upgrading from default pip works"
@@ -2780,7 +2774,7 @@ __install_salt_from_repo() {
fi
fi
rm -f ${_TMP_DIR}/git/deps/*
rm -f /tmp/git/deps/*
echodebug "Installing Salt requirements from PyPi, ${_pip_cmd} install ${_USE_BREAK_SYSTEM_PACKAGES} --ignore-installed ${_PIP_INSTALL_ARGS} -r requirements/static/ci/py${_py_version}/linux.txt"
${_pip_cmd} install ${_USE_BREAK_SYSTEM_PACKAGES} --ignore-installed ${_PIP_INSTALL_ARGS} -r "requirements/static/ci/py${_py_version}/linux.txt"
@@ -2805,7 +2799,7 @@ __install_salt_from_repo() {
echodebug "Running '${_py_exe} setup.py --salt-config-dir=$_SALT_ETC_DIR --salt-cache-dir=${_SALT_CACHE_DIR} ${SETUP_PY_INSTALL_ARGS} bdist_wheel'"
${_py_exe} setup.py --salt-config-dir="$_SALT_ETC_DIR" --salt-cache-dir="${_SALT_CACHE_DIR} ${SETUP_PY_INSTALL_ARGS}" bdist_wheel || return 1
mv dist/salt*.whl ${_TMP_DIR}/git/deps/ || return 1
mv dist/salt*.whl /tmp/git/deps/ || return 1
cd "${__SALT_GIT_CHECKOUT_PARENT_DIR}" || return 1
@@ -2819,14 +2813,14 @@ __install_salt_from_repo() {
${_pip_cmd} install --force-reinstall --break-system-packages "${_arch_dep}"
fi
echodebug "Running '${_pip_cmd} install ${_USE_BREAK_SYSTEM_PACKAGES} --no-deps --force-reinstall ${_PIP_INSTALL_ARGS} ${_TMP_DIR}/git/deps/salt*.whl'"
echodebug "Running '${_pip_cmd} install ${_USE_BREAK_SYSTEM_PACKAGES} --no-deps --force-reinstall ${_PIP_INSTALL_ARGS} /tmp/git/deps/salt*.whl'"
echodebug "Running ${_pip_cmd} install ${_USE_BREAK_SYSTEM_PACKAGES} --no-deps --force-reinstall ${_PIP_INSTALL_ARGS} --global-option=--salt-config-dir=$_SALT_ETC_DIR --salt-cache-dir=${_SALT_CACHE_DIR} ${SETUP_PY_INSTALL_ARGS} ${_TMP_DIR}/git/deps/salt*.whl"
echodebug "Running ${_pip_cmd} install ${_USE_BREAK_SYSTEM_PACKAGES} --no-deps --force-reinstall ${_PIP_INSTALL_ARGS} --global-option=--salt-config-dir=$_SALT_ETC_DIR --salt-cache-dir=${_SALT_CACHE_DIR} ${SETUP_PY_INSTALL_ARGS} /tmp/git/deps/salt*.whl"
${_pip_cmd} install ${_USE_BREAK_SYSTEM_PACKAGES} --no-deps --force-reinstall \
${_PIP_INSTALL_ARGS} \
--global-option="--salt-config-dir=$_SALT_ETC_DIR --salt-cache-dir=${_SALT_CACHE_DIR} ${SETUP_PY_INSTALL_ARGS}" \
${_TMP_DIR}/git/deps/salt*.whl || return 1
/tmp/git/deps/salt*.whl || return 1
echoinfo "Checking if Salt can be imported using ${_py_exe}"
CHECK_SALT_SCRIPT=$(cat << EOM
@@ -6301,8 +6295,8 @@ __get_packagesite_onedir_latest() {
}
__install_saltstack_vmware_photon_os_onedir_repository() {
echodebug "__install_saltstack_vmware_photon_os_onedir_repository() entry"
__install_saltstack_photon_onedir_repository() {
echodebug "__install_saltstack_photon_onedir_repository() entry"
if [ -n "$_PY_EXE" ] && [ "$_PY_MAJOR_VERSION" -ne 3 ]; then
echoerror "Python version is no longer supported, only Python 3"
@@ -6382,8 +6376,8 @@ __install_saltstack_vmware_photon_os_onedir_repository() {
return 0
}
install_vmware_photon_os_deps() {
echodebug "install_vmware_photon_os_deps() entry"
install_photon_deps() {
echodebug "install_photon_deps() entry"
if [ -n "$_PY_EXE" ] && [ "$_PY_MAJOR_VERSION" -ne 3 ]; then
echoerror "Python version is no longer supported, only Python 3"
@@ -6412,8 +6406,8 @@ install_vmware_photon_os_deps() {
return 0
}
install_vmware_photon_os_stable_post() {
echodebug "install_vmware_photon_os_stable_post() entry"
install_photon_stable_post() {
echodebug "install_photon_stable_post() entry"
for fname in api master minion syndic; do
# Skip salt-api since the service should be opt-in and not necessarily started on boot
@@ -6430,8 +6424,8 @@ install_vmware_photon_os_stable_post() {
done
}
install_vmware_photon_os_git_deps() {
echodebug "install_vmware_photon_os_git_deps() entry"
install_photon_git_deps() {
echodebug "install_photon_git_deps() entry"
if [ -n "$_PY_EXE" ] && [ "$_PY_MAJOR_VERSION" -ne 3 ]; then
echoerror "Python version is no longer supported, only Python 3"
@@ -6469,7 +6463,7 @@ install_vmware_photon_os_git_deps() {
__PACKAGES="python${PY_PKG_VER}-devel python${PY_PKG_VER}-pip python${PY_PKG_VER}-setuptools gcc glibc-devel linux-devel.x86_64 cython${PY_PKG_VER}"
echodebug "install_vmware_photon_os_git_deps() distro major version, ${DISTRO_MAJOR_VERSION}"
echodebug "install_photon_git_deps() distro major version, ${DISTRO_MAJOR_VERSION}"
## Photon 5 container is missing systemd on default installation
if [ "${DISTRO_MAJOR_VERSION}" -lt 5 ]; then
@@ -6495,8 +6489,8 @@ install_vmware_photon_os_git_deps() {
return 0
}
install_vmware_photon_os_git() {
echodebug "install_vmware_photon_os_git() entry"
install_photon_git() {
echodebug "install_photon_git() entry"
if [ "${_PY_EXE}" != "" ]; then
_PYEXE=${_PY_EXE}
@@ -6506,7 +6500,7 @@ install_vmware_photon_os_git() {
return 1
fi
install_vmware_photon_os_git_deps
install_photon_git_deps
if [ -f "${_SALT_GIT_CHECKOUT_DIR}/salt/syspaths.py" ]; then
${_PYEXE} setup.py --salt-config-dir="$_SALT_ETC_DIR" --salt-cache-dir="${_SALT_CACHE_DIR}" ${SETUP_PY_INSTALL_ARGS} install --prefix=/usr || return 1
@@ -6516,8 +6510,8 @@ install_vmware_photon_os_git() {
return 0
}
install_vmware_photon_os_git_post() {
echodebug "install_vmware_photon_os_git_post() entry"
install_photon_git_post() {
echodebug "install_photon_git_post() entry"
for fname in api master minion syndic; do
# Skip if not meant to be installed
@@ -6549,9 +6543,9 @@ install_vmware_photon_os_git_post() {
done
}
install_vmware_photon_os_restart_daemons() {
install_photon_restart_daemons() {
[ "$_START_DAEMONS" -eq $BS_FALSE ] && return
echodebug "install_vmware_photon_os_restart_daemons() entry"
echodebug "install_photon_restart_daemons() entry"
for fname in api master minion syndic; do
@@ -6573,8 +6567,8 @@ install_vmware_photon_os_restart_daemons() {
done
}
install_vmware_photon_os_check_services() {
echodebug "install_vmware_photon_os_check_services() entry"
install_photon_check_services() {
echodebug "install_photon_check_services() entry"
for fname in api master minion syndic; do
# Skip salt-api since the service should be opt-in and not necessarily started on boot
@@ -6591,8 +6585,8 @@ install_vmware_photon_os_check_services() {
return 0
}
install_vmware_photon_os_onedir_deps() {
echodebug "install_vmware_photon_os_onedir_deps() entry"
install_photon_onedir_deps() {
echodebug "install_photon_onedir_deps() entry"
if [ "$_UPGRADE_SYS" -eq $BS_TRUE ]; then
@@ -6606,17 +6600,17 @@ install_vmware_photon_os_onedir_deps() {
fi
if [ "$_DISABLE_REPOS" -eq "$BS_FALSE" ]; then
__install_saltstack_vmware_photon_os_onedir_repository || return 1
__install_saltstack_photon_onedir_repository || return 1
fi
# If -R was passed, we need to configure custom repo url with rsync-ed packages
# Which was handled in __install_saltstack_rhel_repository buu that hanlded old-stable which is for
# releases which are End-Of-Life. This call has its own check in case -r was passed without -R.
if [ "$_CUSTOM_REPO_URL" != "null" ]; then
__install_saltstack_vmware_photon_os_onedir_repository || return 1
__install_saltstack_photon_onedir_repository || return 1
fi
__PACKAGES="procps-ng sudo shadow wget"
__PACKAGES="procps-ng sudo shadow"
# shellcheck disable=SC2086
__tdnf_install_noinput ${__PACKAGES} || return 1
@@ -6632,9 +6626,9 @@ install_vmware_photon_os_onedir_deps() {
}
install_vmware_photon_os_onedir() {
install_photon_onedir() {
echodebug "install_vmware_photon_os_onedir() entry"
echodebug "install_photon_onedir() entry"
STABLE_REV=$ONEDIR_REV
_GENERIC_PKG_VERSION=""
@@ -6678,9 +6672,9 @@ install_vmware_photon_os_onedir() {
return 0
}
install_vmware_photon_os_onedir_post() {
install_photon_onedir_post() {
STABLE_REV=$ONEDIR_REV
install_vmware_photon_os_stable_post || return 1
install_photon_stable_post || return 1
return 0
}
@@ -7803,7 +7797,7 @@ install_macosx_git_deps() {
export PATH=/usr/local/bin:$PATH
fi
__fetch_url "${_TMP_DIR}/get-pip.py" "https://bootstrap.pypa.io/get-pip.py" || return 1
__fetch_url "/tmp/get-pip.py" "https://bootstrap.pypa.io/get-pip.py" || return 1
if [ -n "$_PY_EXE" ]; then
_PYEXE="${_PY_EXE}"
@@ -7813,7 +7807,7 @@ install_macosx_git_deps() {
fi
# Install PIP
$_PYEXE ${_TMP_DIR}/get-pip.py || return 1
$_PYEXE /tmp/get-pip.py || return 1
# shellcheck disable=SC2119
__git_clone_and_checkout || return 1
@@ -7825,9 +7819,9 @@ install_macosx_stable() {
install_macosx_stable_deps || return 1
__fetch_url "${_TMP_DIR}/${PKG}" "${SALTPKGCONFURL}" || return 1
__fetch_url "/tmp/${PKG}" "${SALTPKGCONFURL}" || return 1
/usr/sbin/installer -pkg "${_TMP_DIR}/${PKG}" -target / || return 1
/usr/sbin/installer -pkg "/tmp/${PKG}" -target / || return 1
return 0
}
@@ -7836,9 +7830,9 @@ install_macosx_onedir() {
install_macosx_onedir_deps || return 1
__fetch_url "${_TMP_DIR}/${PKG}" "${SALTPKGCONFURL}" || return 1
__fetch_url "/tmp/${PKG}" "${SALTPKGCONFURL}" || return 1
/usr/sbin/installer -pkg "${_TMP_DIR}/${PKG}" -target / || return 1
/usr/sbin/installer -pkg "/tmp/${PKG}" -target / || return 1
return 0
}

View File

@@ -5,17 +5,11 @@ sensoroni:
enabled: False
timeout_ms: 900000
parallel_limit: 5
export:
timeout_ms: 1200000
cache_refresh_interval_ms: 10000
export_metric_limit: 10000
export_event_limit: 10000
csv_separator: ','
node_checkin_interval_ms: 10000
sensoronikey:
soc_host:
suripcap:
pcapMaxCount: 100000
pcapMaxCount: 999999
analyzers:
echotrail:
base_url: https://api.echotrail.io/insights/

View File

@@ -21,13 +21,7 @@
},
{%- endif %}
"importer": {},
"export": {
"timeoutMs": {{ SENSORONIMERGED.config.export.timeout_ms }},
"cacheRefreshIntervalMs": {{ SENSORONIMERGED.config.export.cache_refresh_interval_ms }},
"exportMetricLimit": {{ SENSORONIMERGED.config.export.export_metric_limit }},
"exportEventLimit": {{ SENSORONIMERGED.config.export.export_event_limit }},
"csvSeparator": "{{ SENSORONIMERGED.config.export.csv_separator }}"
},
"export": {},
"statickeyauth": {
"apiKey": "{{ GLOBALS.sensoroni_key }}"
{% if GLOBALS.is_sensor %}

View File

@@ -17,27 +17,6 @@ sensoroni:
description: Parallel limit for the analyzer.
advanced: True
helpLink: cases.html
export:
timeout_ms:
description: Timeout period for the exporter to finish export-related tasks.
advanced: True
helpLink: reports.html
cache_refresh_interval_ms:
description: Refresh interval for cache updates. Longer intervals result in less compute usage but risks stale data included in reports.
advanced: True
helpLink: reports.html
export_metric_limit:
description: Maximum number of metric values to include in each metric aggregation group.
advanced: True
helpLink: reports.html
export_event_limit:
description: Maximum number of events to include per event list.
advanced: True
helpLink: reports.html
csv_separator:
description: Separator character to use for CSV exports.
advanced: False
helpLink: reports.html
node_checkin_interval_ms:
description: Interval in ms to checkin to the soc_host.
advanced: True

View File

@@ -249,6 +249,22 @@ add_readme_custom_local_sigma_repo_template:
- context:
repo_type: "sigma"
create_custom_local_playbooks_repo_template:
git.present:
- name: /nsm/rules/custom-local-repos/local-playbooks
- bare: False
- force: True
add_readme_custom_local_playbooks_repo_template:
file.managed:
- name: /nsm/rules/custom-local-repos/local-playbooks/README
- source: salt://soc/files/soc/detections_custom_repo_template_readme.jinja
- user: 939
- group: 939
- template: jinja
- context:
repo_type: "playbooks"
socore_own_custom_repos:
file.directory:
- name: /nsm/rules/custom-local-repos/

View File

@@ -1364,8 +1364,6 @@ soc:
cases: soc
filedatastore:
jobDir: jobs
retryFailureIntervalMs: 600000
retryFailureMaxAttempts: 5
kratos:
hostUrl:
hydra:
@@ -1489,15 +1487,16 @@ soc:
- repo: https://github.com/Security-Onion-Solutions/securityonion-resources-playbooks
branch: main
folder: securityonion-normalized
- repo: file:///nsm/rules/custom-local-repos/local-playbooks
branch: main
airgap:
- repo: file:///nsm/airgap-resources/playbooks/securityonion-resources-playbooks
branch: main
folder: securityonion-normalized
- repo: file:///nsm/rules/custom-local-repos/local-playbooks
branch: main
assistant:
apiUrl: https://onionai.securityonion.net
healthTimeoutSeconds: 3
systemPromptAddendum: ""
systemPromptAddendumMaxLength: 50000
salt:
queueDir: /opt/sensoroni/queue
timeoutMs: 45000
@@ -1640,9 +1639,6 @@ soc:
- name: socExcludeToggle
filter: 'NOT event.module:"soc"'
enabled: true
- name: onionaiExcludeToggle
filter: 'NOT _index:"*:so-assistant-*"'
enabled: true
queries:
- name: Default Query
description: Show all events grouped by the observer host
@@ -1746,7 +1742,7 @@ soc:
showSubtitle: true
- name: DPD
description: Dynamic Protocol Detection errors
query: '(tags:dpd OR tags:analyzer) | groupby error.reason'
query: 'tags:dpd | groupby error.reason'
showSubtitle: true
- name: Files
description: Files grouped by mimetype
@@ -2012,7 +2008,7 @@ soc:
query: 'tags:dns | groupby dns.query.name | groupby source.ip | groupby -sankey source.ip destination.ip | groupby destination.ip | groupby destination.port | groupby dns.highest_registered_domain | groupby dns.parent_domain | groupby dns.query.type_name | groupby dns.response.code_name | groupby dns.answers.name | groupby destination.as.organization.name'
- name: DPD
description: DPD (Dynamic Protocol Detection) errors
query: '(tags:dpd OR tags:analyzer) | groupby error.reason | groupby -sankey error.reason source.ip | groupby source.ip | groupby -sankey source.ip destination.ip | groupby destination.ip | groupby destination.port | groupby network.protocol | groupby destination.as.organization.name'
query: 'tags:dpd | groupby error.reason | groupby -sankey error.reason source.ip | groupby source.ip | groupby -sankey source.ip destination.ip | groupby destination.ip | groupby destination.port | groupby network.protocol | groupby destination.as.organization.name'
- name: Files
description: Files seen in network traffic
query: 'tags:file | groupby file.mime_type | groupby -sankey file.mime_type file.source | groupby file.source | groupby file.bytes.total | groupby source.ip | groupby destination.ip | groupby destination.as.organization.name'
@@ -2553,33 +2549,10 @@ soc:
level: 'high' # info | low | medium | high | critical
assistant:
enabled: false
investigationPrompt: Investigate Alert ID {socId}
compressContextPrompt: Summarize the conversation for context compaction
investigationPrompt: Investigate Alert ID {socid}
contextLimitSmall: 200000
contextLimitLarge: 1000000
thresholdColorRatioLow: 0.5
thresholdColorRatioMed: 0.75
thresholdColorRatioMax: 1
availableModels:
- id: sonnet-4
displayName: Claude Sonnet 4
contextLimitSmall: 200000
contextLimitLarge: 1000000
lowBalanceColorAlert: 500000
enabled: true
- id: sonnet-4.5
displayName: Claude Sonnet 4.5
contextLimitSmall: 200000
contextLimitLarge: 1000000
lowBalanceColorAlert: 500000
enabled: true
- id: gptoss-120b
displayName: GPT-OSS 120B
contextLimitSmall: 128000
contextLimitLarge: 128000
lowBalanceColorAlert: 500000
enabled: true
- id: qwen-235b
displayName: QWEN 235B
contextLimitSmall: 256000
contextLimitLarge: 256000
lowBalanceColorAlert: 500000
enabled: true

View File

@@ -63,22 +63,18 @@ hypervisor:
required: true
readonly: true
forcedType: int
- field: nsm_size
label: "Size of virtual disk to create and use for /nsm, in GB. Only applicable if no pass-through disk."
forcedType: int
readonly: true
- field: disk
label: "Disk(s) to pass through for /nsm. Free: FREE | Total: TOTAL"
label: "Disk(s) for passthrough. Free: FREE | Total: TOTAL"
readonly: true
options: []
forcedType: '[]int'
- field: copper
label: "Copper port(s) to pass through. Free: FREE | Total: TOTAL"
label: "Copper port(s) for passthrough. Free: FREE | Total: TOTAL"
readonly: true
options: []
forcedType: '[]int'
- field: sfp
label: "SFP port(s) to pass through. Free: FREE | Total: TOTAL"
label: "SFP port(s) for passthrough. Free: FREE | Total: TOTAL"
readonly: true
options: []
forcedType: '[]int'

View File

@@ -3,14 +3,11 @@
{# Define the list of process steps in order (case-sensitive) #}
{% set PROCESS_STEPS = [
'Processing',
'Hypervisor NSM Disk Full',
'IP Configuration',
'Starting Create',
'Executing Deploy Script',
'Initialize Minion Pillars',
'Created Instance',
'Volume Creation',
'Volume Configuration',
'Hardware Configuration',
'Highstate Initiated',
'Destroyed Instance'

View File

@@ -1,51 +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.
#
# Note: Per the Elastic License 2.0, the second limitation states:
#
# "You may not move, change, disable, or circumvent the license key functionality
# in the software, and you may not remove or obscure any functionality in the
# software that is protected by the license key."
{% if 'vrt' in salt['pillar.get']('features', []) %}
{% do salt.log.info('soc/dyanno/hypervisor/remove_failed_vm: Running') %}
{% set vm_name = pillar.get('vm_name') %}
{% set hypervisor = pillar.get('hypervisor') %}
{% if vm_name and hypervisor %}
{% set vm_parts = vm_name.split('_') %}
{% if vm_parts | length >= 2 %}
{% set vm_role = vm_parts[-1] %}
{% set vm_hostname = '_'.join(vm_parts[:-1]) %}
{% set vms_file = '/opt/so/saltstack/local/salt/hypervisor/hosts/' ~ hypervisor ~ 'VMs' %}
{% do salt.log.info('soc/dyanno/hypervisor/remove_failed_vm: Removing VM ' ~ vm_name ~ ' from ' ~ vms_file) %}
remove_vm_{{ vm_name }}_from_vms_file:
module.run:
- name: hypervisor.remove_vm_from_vms_file
- vms_file_path: {{ vms_file }}
- vm_hostname: {{ vm_hostname }}
- vm_role: {{ vm_role }}
{% else %}
{% do salt.log.error('soc/dyanno/hypervisor/remove_failed_vm: Invalid vm_name format: ' ~ vm_name) %}
{% endif %}
{% else %}
{% do salt.log.error('soc/dyanno/hypervisor/remove_failed_vm: Missing required pillar data (vm_name or hypervisor)') %}
{% endif %}
{% do salt.log.info('soc/dyanno/hypervisor/remove_failed_vm: Completed') %}
{% else %}
{% do salt.log.error(
'Hypervisor nodes are a feature supported only for customers with a valid license. '
'Contact Security Onion Solutions, LLC via our website at https://securityonionsolutions.com '
'for more information about purchasing a license to enable this feature.'
) %}
{% endif %}

View File

@@ -13,6 +13,7 @@
{%- import_yaml 'soc/dyanno/hypervisor/hypervisor.yaml' as ANNOTATION -%}
{%- from 'hypervisor/map.jinja' import HYPERVISORS -%}
{%- from 'soc/dyanno/hypervisor/map.jinja' import PROCESS_STEPS -%}
{%- set TEMPLATE = ANNOTATION.hypervisor.hosts.pop('defaultHost') -%}
@@ -26,6 +27,7 @@
{%- if baseDomainStatus == 'Initialized' %}
{%- if vm_list %}
#### Virtual Machines
Status values: {% for step in PROCESS_STEPS %}{{ step }}{% if not loop.last %}, {% endif %}{% endfor %}. "Last Updated" shows when status changed. After "Highstate Initiated", only "Destroyed Instance" updates the timestamp.
| Name | Status | CPU Cores | Memory (GB)| Disk | Copper | SFP | Last Updated |
|--------------------|--------------------|-----------|------------|------|--------|------|---------------------|
@@ -40,29 +42,14 @@
{%- endfor %}
{%- else %}
#### Virtual Machines
Status values: {% for step in PROCESS_STEPS %}{{ step }}{% if not loop.last %}, {% endif %}{% endfor %}. "Last Updated" shows when status changed. After "Highstate Initiated", only "Destroyed Instance" updates the timestamp.
No Virtual Machines Found
{%- endif %}
{%- elif baseDomainStatus == 'ImageDownloadStart' %}
#### INFO
Base domain image download started.
{%- elif baseDomainStatus == 'ImageDownloadFailed' %}
#### ERROR
Base domain image download failed. Please check the salt-master log for details and verify network connectivity.
{%- elif baseDomainStatus == 'SSHKeySetupFailed' %}
#### ERROR
SSH key setup failed. Please check the salt-master log for details.
{%- elif baseDomainStatus == 'SetupFailed' %}
{%- else %}
#### WARNING
Setup failed. Please check the salt-master log for details.
{%- elif baseDomainStatus == 'PreInit' %}
#### WARNING
Base domain has not been initialized. Waiting for hypervisor to highstate.
Base domain has not been initialized.
{%- endif %}
{%- endmacro -%}
@@ -109,21 +96,9 @@ Base domain has not been initialized. Waiting for hypervisor to highstate.
{%- endif -%}
{%- endfor -%}
{# Determine host OS overhead based on role #}
{%- if role == 'hypervisor' -%}
{%- set host_os_cpu = 8 -%}
{%- set host_os_memory = 16 -%}
{%- elif role == 'managerhype' -%}
{%- set host_os_cpu = 16 -%}
{%- set host_os_memory = 32 -%}
{%- else -%}
{%- set host_os_cpu = 0 -%}
{%- set host_os_memory = 0 -%}
{%- endif -%}
{# Calculate available resources (subtract both VM usage and host OS overhead) #}
{%- set cpu_free = hw_config.cpu - ns.used_cpu - host_os_cpu -%}
{%- set mem_free = hw_config.memory - ns.used_memory - host_os_memory -%}
{# Calculate available resources #}
{%- set cpu_free = hw_config.cpu - ns.used_cpu -%}
{%- set mem_free = hw_config.memory - ns.used_memory -%}
{# Get used PCI indices #}
{%- set used_disk = [] -%}

View File

@@ -237,22 +237,10 @@ function manage_salt() {
case "$op" in
state)
log "Performing '$op' for '$state' on minion '$minion'"
state=$(echo "$request" | jq -r .state)
async=$(echo "$request" | jq -r .async)
if [[ $async == "true" ]]; then
log "Performing async '$op' on minion $minion with state '$state'"
response=$(salt --async "$minion" state.apply "$state" queue=2)
else
log "Performing '$op' on minion $minion with state '$state'"
response=$(salt "$minion" state.apply "$state")
fi
exit_code=$?
if [[ $exit_code -ne 0 && "$response" =~ "is running as PID" ]]; then
log "Salt already running: $response ($exit_code)"
respond "$id" "ERROR_SALT_ALREADY_RUNNING"
return
fi
;;
highstate)
log "Performing '$op' on minion $minion"
@@ -271,7 +259,7 @@ function manage_salt() {
;;
esac
if [[ $exit_code -eq 0 ]]; then
if [[ exit_code -eq 0 ]]; then
log "Successful command execution: $response"
respond "$id" "true"
else

View File

@@ -91,4 +91,51 @@ Finally, commit it:
The next time the Elastalert / Sigma engine syncs, the new rule should be imported
If there are errors, review the sync log to troubleshoot further.
{% elif repo_type == 'playbooks' %}
# Playbooks Local Custom Repository
This folder has already been initialized as a git repo
and your Security Onion grid is configured to import any Playbook files found here.
Just add your playbook file and commit it.
For example:
** Note: If this is your first time making changes to this repo, you may run into the following error:
fatal: detected dubious ownership in repository at '/nsm/rules/custom-local-repos/local-playbooks'
To add an exception for this directory, call:
git config --global --add safe.directory /nsm/rules/custom-local-repos/local-playbooks
This means that the user you are running commands as does not match the user that is used for this git repo (socore).
You will need to make sure your playbook files are accessible to the socore user, so either su to socore
or add the exception and then chown the playbook files later.
Also, you will be asked to set some configuration:
```
Author identity unknown
*** Please tell me who you are.
Run
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
to set your account's default identity.
Omit --global to set the identity only in this repository.
```
Run these commands, ommitting the `--global`.
With that out of the way:
First, create the playbook file with a .yml or .yaml extension:
`vi my_custom_playbook.yml`
Next, use git to stage the new playbook to be committed:
`git add my_custom_playbook.yml`
Finally, commit it:
`git commit -m "Initial commit of my_custom_playbook.yml"`
The next time SOC restarts, the new playbook should be imported
If there are errors, review the SOC log to troubleshoot further.
{% endif %}

View File

@@ -424,17 +424,6 @@ soc:
description: The maximum number of documents to request in a single Elasticsearch scroll request.
bulkIndexWorkerCount:
description: The number of worker threads to use when bulk indexing data into Elasticsearch. A value below 1 will default to the number of CPUs available.
filedatastore:
jobDir:
description: The location where local job files are stored on the manager.
global: True
advanced: True
retryFailureIntervalMs:
description: The interval, in milliseconds, to wait before attempting to reprocess a failed job.
global: True
retryFailureMaxAttempts:
description: The max number of attempts to process a job, in the event the job fails to complete.
global: True
sostatus:
refreshIntervalMs:
description: Duration (in milliseconds) between refreshes of the grid status. Shortening this duration may not have expected results, as the backend systems feeding this sostatus data will continue their updates as scheduled.
@@ -596,19 +585,6 @@ soc:
description: The URL of the AI gateway.
advanced: True
global: True
healthTimeoutSeconds:
description: Timeout in seconds for the Onion AI health check.
global: True
advanced: True
systemPromptAddendum:
description: Additional context to provide to the AI assistant about this SOC deployment. This can include information about your environment, policies, or any other relevant details that can help the AI provide more accurate and tailored assistance. Long prompts may be shortened.
global: True
advanced: False
multiline: True
systemPromptAddendumMaxLength:
description: Maximum length of the system prompt addendum. Longer prompts will be truncated.
global: True
advanced: True
client:
assistant:
enabled:
@@ -617,9 +593,14 @@ soc:
investigationPrompt:
description: Prompt given to Onion AI when beginning an investigation.
global: True
compressContextPrompt:
description: Prompt given to Onion AI when summarizing a conversation in order to compress context.
contextLimitSmall:
description: Smaller context limit for Onion AI.
global: True
advanced: True
contextLimitLarge:
description: Larger context limit for Onion AI.
global: True
advanced: True
thresholdColorRatioLow:
description: Lower visual context color change threshold.
global: True
@@ -634,37 +615,7 @@ soc:
advanced: True
lowBalanceColorAlert:
description: Onion AI credit amount at which balance turns red.
global: True
advanced: True
availableModels:
description: List of AI models available for use in SOC as well as model specific warning thresholds.
global: True
advanced: True
forcedType: "[]{}"
helpLink: assistant.html
syntax: json
uiElements:
- field: id
label: Model ID
required: True
- field: displayName
label: Display Name
required: True
- field: contextLimitSmall
label: Context Limit (Small)
forcedType: int
required: True
- field: contextLimitLarge
label: Context Limit (Large)
forcedType: int
required: True
- field: lowBalanceColorAlert
label: Low Balance Color Alert
forcedType: int
required: True
- field: enabled
label: Enabled
forcedType: bool
apiTimeoutMs:
description: Duration (in milliseconds) to wait for a response from the SOC server API before giving up and showing an error on the SOC UI.
global: True

View File

@@ -4,17 +4,10 @@
# Elastic License 2.0.
{% set nvme_devices = salt['cmd.shell']("ls /dev/nvme*n1 2>/dev/null || echo ''") %}
{% set virtio_devices = salt['cmd.shell']("test -b /dev/vdb && echo '/dev/vdb' || echo ''") %}
{% set nvme_devices = salt['cmd.shell']("find /dev -name 'nvme*n1' 2>/dev/null") %}
{% if nvme_devices %}
include:
- storage.nsm_mount_nvme
{% elif virtio_devices %}
include:
- storage.nsm_mount_virtio
- storage.nsm_mount
{% endif %}

View File

@@ -22,8 +22,8 @@ storage_nsm_mount_logdir:
# Install the NSM mount script
storage_nsm_mount_script:
file.managed:
- name: /usr/sbin/so-nsm-mount-nvme
- source: salt://storage/tools/sbin/so-nsm-mount-nvme
- name: /usr/sbin/so-nsm-mount
- source: salt://storage/tools/sbin/so-nsm-mount
- mode: 755
- user: root
- group: root
@@ -34,7 +34,7 @@ storage_nsm_mount_script:
# Execute the mount script if not already mounted
storage_nsm_mount_execute:
cmd.run:
- name: /usr/sbin/so-nsm-mount-nvme
- name: /usr/sbin/so-nsm-mount
- unless: mountpoint -q /nsm
- require:
- file: storage_nsm_mount_script

View File

@@ -1,39 +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.
# Install required packages
storage_nsm_mount_virtio_packages:
pkg.installed:
- pkgs:
- xfsprogs
# Ensure log directory exists
storage_nsm_mount_virtio_logdir:
file.directory:
- name: /opt/so/log
- makedirs: True
- user: root
- group: root
- mode: 755
# Install the NSM mount script
storage_nsm_mount_virtio_script:
file.managed:
- name: /usr/sbin/so-nsm-mount-virtio
- source: salt://storage/tools/sbin/so-nsm-mount-virtio
- mode: 755
- user: root
- group: root
- require:
- pkg: storage_nsm_mount_virtio_packages
- file: storage_nsm_mount_virtio_logdir
# Execute the mount script if not already mounted
storage_nsm_mount_virtio_execute:
cmd.run:
- name: /usr/sbin/so-nsm-mount-virtio
- unless: mountpoint -q /nsm
- require:
- file: storage_nsm_mount_virtio_script

View File

@@ -81,7 +81,7 @@
set -e
LOG_FILE="/opt/so/log/so-nsm-mount-nvme"
LOG_FILE="/opt/so/log/so-nsm-mount.log"
VG_NAME=""
LV_NAME="nsm"
MOUNT_POINT="/nsm"

View File

@@ -1,171 +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.
# Usage:
# so-nsm-mount-virtio
#
# Options:
# None - script automatically configures /dev/vdb
#
# Examples:
# 1. Configure and mount virtio-blk device:
# ```bash
# sudo so-nsm-mount-virtio
# ```
#
# Notes:
# - Requires root privileges
# - Mounts /dev/vdb as /nsm
# - Creates XFS filesystem if needed
# - Configures persistent mount via /etc/fstab
# - Safe to run multiple times
#
# Description:
# This script automates the configuration and mounting of virtio-blk devices
# as /nsm in Security Onion virtual machines. It performs these steps:
#
# Dependencies:
# - xfsprogs: Required for XFS filesystem operations
#
# 1. Safety Checks:
# - Verifies root privileges
# - Checks if /nsm is already mounted
# - Verifies /dev/vdb exists
#
# 2. Filesystem Creation:
# - Creates XFS filesystem on /dev/vdb if not already formatted
#
# 3. Mount Configuration:
# - Creates /nsm directory if needed
# - Adds entry to /etc/fstab for persistence
# - Mounts the filesystem as /nsm
#
# Exit Codes:
# 0: Success conditions:
# - Device configured and mounted
# - Already properly mounted
# 1: Error conditions:
# - Must be run as root
# - Device /dev/vdb not found
# - Filesystem creation failed
# - Mount operation failed
#
# Logging:
# - All operations logged to /opt/so/log/so-nsm-mount-virtio
set -e
LOG_FILE="/opt/so/log/so-nsm-mount-virtio"
DEVICE="/dev/vdb"
MOUNT_POINT="/nsm"
# Function to log messages
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') $1" | tee -a "$LOG_FILE"
}
# Function to log errors
log_error() {
echo "$(date '+%Y-%m-%d %H:%M:%S') ERROR: $1" | tee -a "$LOG_FILE" >&2
}
# Function to check if running as root
check_root() {
if [ "$EUID" -ne 0 ]; then
log_error "Must be run as root"
exit 1
fi
}
# Main execution
main() {
log "=========================================="
log "Starting virtio-blk NSM mount process"
log "=========================================="
# Check root privileges
check_root
# Check if already mounted
if mountpoint -q "$MOUNT_POINT"; then
log "$MOUNT_POINT is already mounted"
log "=========================================="
exit 0
fi
# Check if device exists
if [ ! -b "$DEVICE" ]; then
log_error "Device $DEVICE not found"
log "=========================================="
exit 1
fi
log "Found device: $DEVICE"
# Get device size
local size=$(lsblk -dbn -o SIZE "$DEVICE" 2>/dev/null | numfmt --to=iec)
log "Device size: $size"
# Check if device has filesystem
if ! blkid "$DEVICE" | grep -q 'TYPE="xfs"'; then
log "Creating XFS filesystem on $DEVICE"
if ! mkfs.xfs -f "$DEVICE" 2>&1 | tee -a "$LOG_FILE"; then
log_error "Failed to create filesystem"
log "=========================================="
exit 1
fi
log "Filesystem created successfully"
else
log "Device already has XFS filesystem"
fi
# Create mount point
if [ ! -d "$MOUNT_POINT" ]; then
log "Creating mount point $MOUNT_POINT"
mkdir -p "$MOUNT_POINT"
fi
# Add to fstab if not present
if ! grep -q "$DEVICE.*$MOUNT_POINT" /etc/fstab; then
log "Adding entry to /etc/fstab"
echo "$DEVICE $MOUNT_POINT xfs defaults 0 0" >> /etc/fstab
log "Entry added to /etc/fstab"
else
log "Entry already exists in /etc/fstab"
fi
# Mount the filesystem
log "Mounting $DEVICE to $MOUNT_POINT"
if mount "$MOUNT_POINT" 2>&1 | tee -a "$LOG_FILE"; then
log "Successfully mounted $DEVICE to $MOUNT_POINT"
# Verify mount
if mountpoint -q "$MOUNT_POINT"; then
log "Mount verified successfully"
# Display mount information
log "Mount details:"
df -h "$MOUNT_POINT" | tail -n 1 | tee -a "$LOG_FILE"
else
log_error "Mount verification failed"
log "=========================================="
exit 1
fi
else
log_error "Failed to mount $DEVICE"
log "=========================================="
exit 1
fi
log "=========================================="
log "Virtio-blk NSM mount process completed successfully"
log "=========================================="
exit 0
}
# Run main function
main

View File

@@ -14,7 +14,7 @@ include:
strelka_filestream:
docker_container.running:
- image: {{ GLOBALS.registry_host }}:5000/{{ GLOBALS.image_repo }}/so-strelka-manager:{{ GLOBALS.so_version }}
- image: {{ GLOBALS.registry_host }}:5000/{{ GLOBALS.image_repo }}/so-strelka-filestream:{{ GLOBALS.so_version }}
- binds:
- /opt/so/conf/strelka/filestream/:/etc/strelka/:ro
- /nsm/strelka:/nsm/strelka

View File

@@ -14,7 +14,7 @@ include:
strelka_frontend:
docker_container.running:
- image: {{ GLOBALS.registry_host }}:5000/{{ GLOBALS.image_repo }}/so-strelka-manager:{{ GLOBALS.so_version }}
- image: {{ GLOBALS.registry_host }}:5000/{{ GLOBALS.image_repo }}/so-strelka-frontend:{{ GLOBALS.so_version }}
- binds:
- /opt/so/conf/strelka/frontend/:/etc/strelka/:ro
- /nsm/strelka/log/:/var/log/strelka/:rw

View File

@@ -7,47 +7,9 @@
{% if sls.split('.')[0] in allowed_states %}
{% from 'vars/globals.map.jinja' import GLOBALS %}
{% from 'bpf/suricata.map.jinja' import SURICATABPF %}
{% from 'suricata/map.jinja' import SURICATAMERGED %}
{% from 'bpf/suricata.map.jinja' import SURICATABPF, SURICATA_BPF_STATUS, SURICATA_BPF_CALC %}
suridir:
file.directory:
- name: /opt/so/conf/suricata
- user: 940
- group: 940
{% if GLOBALS.pcap_engine in ["SURICATA", "TRANSITION"] %}
{% from 'bpf/pcap.map.jinja' import PCAPBPF, PCAP_BPF_STATUS, PCAP_BPF_CALC %}
# BPF compilation and configuration
{% if PCAPBPF and not PCAP_BPF_STATUS %}
suriPCAPbpfcompilationfailure:
test.configurable_test_state:
- changes: False
- result: False
- comment: "BPF Syntax Error - Discarding Specified BPF. Error: {{ PCAP_BPF_CALC['stderr'] }}"
{% endif %}
{% endif %}
# BPF applied to all of Suricata - alerts/metadata/pcap
suribpf:
file.managed:
- name: /opt/so/conf/suricata/bpf
- user: 940
- group: 940
{% if SURICATA_BPF_STATUS %}
- contents: {{ SURICATABPF }}
{% else %}
- contents:
- ""
{% endif %}
{% if SURICATABPF and not SURICATA_BPF_STATUS %}
suribpfcompilationfailure:
test.configurable_test_state:
- changes: False
- result: False
- comment: "BPF Syntax Error - Discarding Specified BPF. Error: {{ SURICATA_BPF_CALC['stderr'] }}"
{% endif %}
{% set BPF_STATUS = 0 %}
# Add Suricata Group
suricatagroup:
@@ -87,11 +49,18 @@ suricata_sbin_jinja:
- file_mode: 755
- template: jinja
suridir:
file.directory:
- name: /opt/so/conf/suricata
- user: 940
- group: 940
suriruledir:
file.directory:
- name: /opt/so/conf/suricata/rules
- user: 940
- group: 940
- makedirs: True
surilogdir:
file.directory:
@@ -167,6 +136,32 @@ suriclassifications:
- user: 940
- group: 940
# BPF compilation and configuration
{% if SURICATABPF %}
{% set BPF_CALC = salt['cmd.script']('salt://common/tools/sbin/so-bpf-compile', GLOBALS.sensor.interface + ' ' + SURICATABPF|join(" "),cwd='/root') %}
{% if BPF_CALC['stderr'] == "" %}
{% set BPF_STATUS = 1 %}
{% else %}
suribpfcompilationfailure:
test.configurable_test_state:
- changes: False
- result: False
- comment: "BPF Syntax Error - Discarding Specified BPF"
{% endif %}
{% endif %}
suribpf:
file.managed:
- name: /opt/so/conf/suricata/bpf
- user: 940
- group: 940
{% if BPF_STATUS %}
- contents: {{ SURICATABPF }}
{% else %}
- contents:
- ""
{% endif %}
so-suricata-eve-clean:
file.managed:
- name: /usr/sbin/so-suricata-eve-clean

View File

@@ -34,7 +34,7 @@ suricata:
threads: 1
tpacket-v3: "yes"
ring-size: 5000
block-size: 69632
block-size: 32768
block-timeout: 10
use-emergency-flush: "yes"
buffer-size: 32768
@@ -97,11 +97,6 @@ suricata:
- 4789
TEREDO_PORTS:
- 3544
SIP_PORTS:
- 5060
- 5061
GENEVE_PORTS:
- 6081
default-log-dir: /var/log/suricata/
stats:
enabled: "yes"
@@ -139,6 +134,14 @@ suricata:
header: X-Forwarded-For
unified2-alert:
enabled: "no"
http-log:
enabled: "no"
filename: http.log
append: "yes"
tls-log:
enabled: "no"
filename: tls.log
append: "yes"
tls-store:
enabled: "no"
pcap-log:
@@ -154,6 +157,9 @@ suricata:
totals: "yes"
threads: "no"
null-values: "yes"
syslog:
enabled: "no"
facility: local5
drop:
enabled: "no"
file-store:
@@ -200,9 +206,6 @@ suricata:
enabled: "yes"
detection-ports:
dp: 443
ja3-fingerprints: auto
ja4-fingerprints: auto
encryption-handling: track-only
dcerpc:
enabled: "yes"
ftp:
@@ -252,21 +255,19 @@ suricata:
libhtp:
default-config:
personality: IDS
request-body-limit: 100 KiB
response-body-limit: 100 KiB
request-body-minimal-inspect-size: 32 KiB
request-body-inspect-window: 4 KiB
response-body-minimal-inspect-size: 40 KiB
response-body-inspect-window: 16 KiB
request-body-limit: 100kb
response-body-limit: 100kb
request-body-minimal-inspect-size: 32kb
request-body-inspect-window: 4kb
response-body-minimal-inspect-size: 40kb
response-body-inspect-window: 16kb
response-body-decompress-layer-limit: 2
http-body-inline: auto
swf-decompression:
enabled: "no"
enabled: "yes"
type: both
compress-depth: 100 KiB
decompress-depth: 100 KiB
randomize-inspection-sizes: "yes"
randomize-inspection-range: 10
compress-depth: 0
decompress-depth: 0
double-decode-path: "no"
double-decode-query: "no"
server-config:
@@ -400,12 +401,8 @@ suricata:
vxlan:
enabled: true
ports: $VXLAN_PORTS
geneve:
erspan:
enabled: true
ports: $GENEVE_PORTS
max-layers: 16
recursion-level:
use-for-tracking: true
detect:
profile: medium
custom-values:
@@ -425,12 +422,7 @@ suricata:
spm-algo: auto
luajit:
states: 128
security:
lua:
allow-rules: false
max-bytes: 500000
max-instructions: 500000
allow-restricted-functions: false
profiling:
rules:
enabled: "yes"

Some files were not shown because too many files have changed in this diff Show More