Compare commits

...

145 Commits

Author SHA1 Message Date
reyesj2
0b127582cb 2.4.200 soup changes 2025-12-03 20:49:25 -06:00
reyesj2
6e9b8791c8 Merge branch '2.4/dev' of github.com:Security-Onion-Solutions/securityonion into reyesj2/advilm 2025-12-03 20:27:13 -06:00
reyesj2
ef87ad77c3 Merge branch 'reyesj2/advilm' of github.com:Security-Onion-Solutions/securityonion into reyesj2/advilm 2025-12-03 20:23:03 -06:00
reyesj2
8477420911 logstash adv config state file 2025-12-03 20:10:06 -06:00
Jason Ertel
f5741e318f Merge pull request #15281 from Security-Onion-Solutions/jertel/wip
skip continue prompt if user cannot actually continue
2025-12-03 16:37:07 -05:00
Josh Patterson
e010b5680a Merge pull request #15280 from Security-Onion-Solutions/reservegid
reserve group ids
2025-12-03 16:24:12 -05:00
Josh Patterson
8620d3987e add saltgid 2025-12-03 15:04:28 -05:00
Jason Ertel
30487a54c1 skip continue prompt if user cannot actually contine 2025-12-03 11:52:10 -05:00
Josh Patterson
aed27fa111 reserve group ids 2025-12-03 11:19:46 -05:00
Jorge Reyes
8d2701e143 Merge branch '2.4/dev' into reyesj2/advilm 2025-12-02 15:42:15 -06:00
reyesj2
877444ac29 cert update is a forced update 2025-12-02 15:16:59 -06:00
reyesj2
b0d9426f1b automated cert update for kafka fleet output policy 2025-12-02 15:11:00 -06:00
reyesj2
18accae47e annotation typo 2025-12-02 15:10:29 -06:00
Josh Patterson
55e3a2c6b6 Merge pull request #15277 from Security-Onion-Solutions/soyamllistremove
need additional line bw class
2025-12-02 15:09:47 -05:00
Josh Patterson
ef092e2893 rename to removelistitem 2025-12-02 15:01:32 -05:00
Josh Patterson
89eb95c077 add removefromlist 2025-12-02 14:46:24 -05:00
Josh Patterson
e871ec358e need additional line bw class 2025-12-02 14:43:33 -05:00
Josh Patterson
271a2f74ad Merge pull request #15275 from Security-Onion-Solutions/soyamllistremove
add new so-yaml_test for removefromlist
2025-12-02 14:34:09 -05:00
Josh Patterson
d6bd951c37 add new so-yaml_test for removefromlist 2025-12-02 14:31:57 -05:00
reyesj2
45a8c0acd1 merge 2.4/dev 2025-12-02 11:16:08 -06:00
Jorge Reyes
6fbed2dd9f Merge pull request #15264 from Security-Onion-Solutions/reyesj2-patch-2
add force & certs flag to update fleet certs as needed
2025-12-01 11:11:25 -06:00
Mike Reeves
875de88cb4 Merge pull request #15271 from Security-Onion-Solutions/TOoSmOotH-patch-2
Add JA4D option to config.zeek.ja4
2025-12-01 10:03:12 -05:00
Mike Reeves
63bb44886e Add JA4D option to config.zeek.ja4 2025-12-01 10:00:42 -05:00
reyesj2
edf3c9464f add --certs flag to update certs. Used with --force, to ensure certs are updated even if hosts update isn't needed 2025-11-25 16:16:19 -06:00
reyesj2
cc8fb96047 valid config for number_of_replicas in allocate action includes 0 2025-11-24 11:12:09 -06:00
reyesj2
3339b50daf drop forcemerge when max_num_segements doesn't exist or empty 2025-11-21 16:39:45 -06:00
reyesj2
415ea07a4f clean up 2025-11-21 16:04:26 -06:00
reyesj2
b80ec95fa8 update regex, revert to default will allow setting value back to '' | None 2025-11-21 14:41:03 -06:00
reyesj2
99cb51482f unneeded 'set' 2025-11-21 14:32:58 -06:00
reyesj2
90638f7a43 Merge branch 'reyesj2/advea' into reyesj2/advilm 2025-11-21 14:25:28 -06:00
reyesj2
1fb00c8eb6 update so-elastic-fleet-outputs-update to use advanced output options when set, else empty "". Also trigger update_logstash_outputs() when hash of config_yaml has changed 2025-11-21 14:22:42 -06:00
reyesj2
4490ea7635 format EA logstash output adv config items 2025-11-21 14:21:17 -06:00
reyesj2
bce7a20d8b soc configurable EA logstash output adv settings 2025-11-21 14:19:51 -06:00
Josh Patterson
9c06713f32 Merge pull request #15251 from Security-Onion-Solutions/bravo
use timestamp in volume path to prevent duplicates
2025-11-21 14:54:30 -05:00
Josh Patterson
23da0d4ba0 use timestamp in filename to prevent duplicates 2025-11-21 14:49:03 -05:00
Josh Patterson
d5f2cfb354 Merge pull request #15248 from Security-Onion-Solutions/bravo
clarify hypervisor annotation
2025-11-20 17:28:32 -05:00
Josh Patterson
fb5ad4193d indicate base image download start 2025-11-20 17:13:36 -05:00
Josh Patterson
1f5f283c06 update hypervisor annotaion. preinit instead of initialized 2025-11-20 16:53:55 -05:00
Josh Patterson
cf048030c4 Merge pull request #15247 from Security-Onion-Solutions/bravo
Notify user of hypervisor environment setup failures
2025-11-20 16:04:49 -05:00
Josh Patterson
2d716b44a8 update comment 2025-11-20 15:52:21 -05:00
Jorge Reyes
d70d652310 Merge pull request #15244 from Security-Onion-Solutions/reyesj2/suricapfile
suricata capture file
2025-11-20 14:31:43 -06:00
reyesj2
c5db7c8752 suricata.capture_file keyword 2025-11-20 14:26:12 -06:00
reyesj2
6f42ff3442 suricata capture_file 2025-11-20 14:16:49 -06:00
reyesj2
433dab7376 format json 2025-11-20 14:16:10 -06:00
Josh Patterson
97c1a46013 update annotation for general failure 2025-11-20 15:08:04 -05:00
Josh Patterson
fbe97221bb set initialized status 2025-11-20 14:43:09 -05:00
Josh Patterson
841ce6b6ec update hypervisor annotation for image download or ssh key creation failure 2025-11-20 13:55:22 -05:00
Josh Patterson
dd0b4c3820 fix failed or hung qcow2 image download 2025-11-19 15:48:53 -05:00
reyesj2
b52dd53e29 advanced ilm actions 2025-11-19 13:24:55 -06:00
reyesj2
a155f45036 always update annotation / defaults for managed integrations 2025-11-19 13:24:29 -06:00
Josh Patterson
b407c68d88 Merge remote-tracking branch 'origin/2.4/dev' into bravo 2025-11-19 10:23:11 -05:00
Josh Patterson
5b6a7035af need python_shell for pipes 2025-11-19 10:22:58 -05:00
Jason Ertel
12d490ad4a Merge pull request #15240 from Security-Onion-Solutions/jertel/wip
communicate to the viewer that OS patches may take some time
2025-11-19 10:01:03 -05:00
Jason Ertel
76cbd18d2c communicate to the viewer that OS patches may take some time 2025-11-19 09:56:42 -05:00
Jorge Reyes
a7337c95e1 Merge pull request #15234 from Security-Onion-Solutions/reyesj2/pipeline-upd
update zeek pipelines
2025-11-17 10:36:10 -06:00
Josh Patterson
3f7c3326ea Merge pull request #15237 from Security-Onion-Solutions/bravo
rm salt keyring and repo file for deb
2025-11-17 09:27:53 -05:00
Josh Patterson
bf41de8c14 rm salt keyring and repo file for deb 2025-11-17 08:56:02 -05:00
reyesj2
de4424fab0 remove typos 2025-11-14 19:15:51 -06:00
reyesj2
136a829509 detect-sqli deprecated in favor of detect-sql-injection 2025-11-14 16:51:00 -06:00
reyesj2
bcec999be4 zeek.dns reduce errors 2025-11-14 15:47:29 -06:00
reyesj2
7c73b4713f update analyzer pipeline 2025-11-14 15:47:29 -06:00
reyesj2
45b4b1d963 ingest zeek analyzer.log + update dpd dashboard with analyzer tag 2025-11-14 15:47:29 -06:00
reyesj2
fcfd74ec1e zeek.analyzer format json 2025-11-14 15:47:29 -06:00
reyesj2
68b0cd7549 rename zeek.dpd zeek.analyzer 2025-11-14 15:47:29 -06:00
reyesj2
715d801ce8 format json zeek.dns 2025-11-14 15:47:19 -06:00
Jorge Reyes
4a810696e7 Merge pull request #15231 from Security-Onion-Solutions/reyesj2/bond0
fix so-setup error duplicate bond0
2025-11-14 12:12:46 -06:00
reyesj2
6b525a2c21 fix so-setup error duplicate bond0 2025-11-14 11:19:32 -06:00
Jorge Reyes
a5d8385f07 Merge pull request #15230 from Security-Onion-Solutions/reyesj2/pipeline-upd
suricata pipeline updates
2025-11-14 10:43:33 -06:00
reyesj2
211bf7e77b ignore errors on tld script 2025-11-14 09:25:19 -06:00
reyesj2
1542b74133 move dns tld fields to its own pipeline 2025-11-14 09:24:58 -06:00
reyesj2
4314c79f85 bump suricata dns logging version 2025-11-14 08:24:31 -06:00
reyesj2
da9717bc79 don't attempt rename if field doesn't exist -- reducing pipeline stat errors 2025-11-14 08:15:40 -06:00
Jason Ertel
045cf7866c Merge pull request #15225 from Security-Onion-Solutions/jertel/wip
pcap annotations
2025-11-14 08:37:37 -05:00
reyesj2
431e0b0780 format suricata.alert json 2025-11-13 19:29:50 -06:00
reyesj2
e782266caa suricata 8 dns v3 2025-11-13 19:21:31 -06:00
coreyogburn
a4666b2c08 Merge pull request #15229 from Security-Onion-Solutions/cogburn/toggle-models
Add Enabled Flag to Models
2025-11-13 16:13:24 -07:00
Corey Ogburn
dcc3206e51 Add Enabled Flag to Models 2025-11-13 15:32:28 -07:00
Josh Patterson
8358b6ea6f Merge pull request #15228 from Security-Onion-Solutions/bravo
wait for 200 from registry before proceeding
2025-11-13 16:34:43 -05:00
coreyogburn
d1a66a91c6 Merge pull request #15221 from Security-Onion-Solutions/cogburn/compress-context
CompressContextPrompt
2025-11-13 14:33:56 -07:00
Josh Patterson
7fdcb92614 wait for 200 from registry before proceeding 2025-11-13 16:30:58 -05:00
Jason Ertel
cec1890b6b pcap annotations 2025-11-13 16:15:47 -05:00
Corey Ogburn
b1b66045ea Change in prompt wording 2025-11-13 12:08:47 -07:00
Corey Ogburn
33b22bf2e4 Shorten Prompt 2025-11-13 11:09:09 -07:00
Corey Ogburn
3a38886345 CompressContextPrompt 2025-11-13 11:09:08 -07:00
reyesj2
7be70faab6 format json 2025-11-13 10:49:37 -06:00
Josh Patterson
2729fdbea6 Merge pull request #15223 from Security-Onion-Solutions/bravo
configure salt, then install. update bootstrap-salt. reduce salt install fail timeout
2025-11-13 11:35:43 -05:00
Jorge Reyes
bfd08d1d2e Merge pull request #15204 from Security-Onion-Solutions/reyesj2/retention
update so-elasticsearch-retention-estimate
2025-11-13 10:05:49 -06:00
Josh Patterson
fed75c7b39 use -r with bootstrap to disable script repo 2025-11-12 19:47:25 -05:00
Josh Patterson
3427df2a54 update bootstrap-salt to latest 2025-11-12 18:07:14 -05:00
Josh Patterson
be11c718f6 configure salt then install it 2025-11-12 18:06:55 -05:00
Josh Patterson
235dfd78f1 Revert "salt-minion service KillMode to control-group"
This reverts commit 7c8b9b4374.
2025-11-12 14:20:28 -05:00
Josh Patterson
7c8b9b4374 salt-minion service KillMode to control-group 2025-11-12 12:30:29 -05:00
Jason Ertel
7762faf075 Merge pull request #15219 from Security-Onion-Solutions/jertel/wip
add support to so-yaml for using yaml file content for values
2025-11-12 08:12:23 -05:00
Jason Ertel
80fbb31372 fix test 2025-11-11 17:04:19 -05:00
Jason Ertel
7c45db2295 add support to so-yaml for using yaml file content for values 2025-11-11 16:57:54 -05:00
Jason Ertel
0545e1d33b add support to so-yaml for using yaml file content for values 2025-11-11 16:55:00 -05:00
Jason Ertel
08147e27b0 Merge pull request #15213 from Security-Onion-Solutions/jertel/wip
reduce pcapMaxCount to fit better with max upload size
2025-11-10 19:08:58 -05:00
Josh Patterson
c9153617be Merge pull request #15211 from Security-Onion-Solutions/bravo
Suricata 8.0.2
2025-11-10 17:09:43 -05:00
Josh Patterson
245ceb2d49 suricata defaults and annotation 2025-11-10 16:40:11 -05:00
Jason Ertel
4c65975907 reduce pcapMaxCount to fit better with max upload size 2025-11-10 15:44:05 -05:00
Mike Reeves
dfef7036ce Merge pull request #15209 from Security-Onion-Solutions/TOoSmOotH-patch-1
Update defaults.yaml
2025-11-10 14:53:00 -05:00
Mike Reeves
44594ba726 Update defaults.yaml 2025-11-10 14:24:27 -05:00
Josh Patterson
1876c4d9df fix var name 2025-11-10 14:16:16 -05:00
Josh Patterson
a2ff66b5d0 update annotation 2025-11-10 14:12:20 -05:00
Josh Patterson
e3972dc5af Merge remote-tracking branch 'origin/2.4/dev' into bravo 2025-11-10 13:28:42 -05:00
Josh Patterson
18c0f197b2 suricata bpf 2025-11-10 13:28:19 -05:00
Jorge Reyes
5b371c220c Merge pull request #15207 from Security-Onion-Solutions/reyesj2/forwardnode-sensor 2025-11-10 08:46:12 -06:00
Josh Patterson
78c193f0a2 handle bpf for suricata 8 pcap 2025-11-07 17:40:24 -05:00
Josh Patterson
274295bc97 return exit codes 2025-11-07 17:39:13 -05:00
Josh Patterson
6c7ef622c1 spaces removed from expected output 2025-11-07 17:08:33 -05:00
Josh Patterson
da1cac0d53 tls-log, http-log and syslog outputs deprecated https://github.com/Security-Onion-Solutions/securityonion/issues/15203 2025-11-06 16:32:55 -05:00
reyesj2
a84df14137 rename forward node -> sensor node 2025-11-06 15:23:55 -06:00
Jorge Reyes
4a49f9d004 Merge branch '2.4/dev' into reyesj2/retention 2025-11-06 14:29:08 -06:00
reyesj2
1eb4b5379a show 30d scheduled deletions or 7d scheduled deletions depending on what historical data is available 2025-11-06 14:25:25 -06:00
reyesj2
35c7fc06d7 fix bug showing duplicate backing indices in recommendations 2025-11-06 14:24:58 -06:00
reyesj2
b69d453a68 typo 2025-11-06 14:24:29 -06:00
Josh Patterson
b7e1989d45 resolve block-size not large enough for max fragmented IP packet size warning 2025-11-06 09:49:46 -05:00
Jorge Reyes
202b03b32b Merge pull request #15201 from Security-Onion-Solutions/reyesj2-patch-5
update so-elasticsearch-retention-estimate
2025-11-06 08:18:38 -06:00
reyesj2
1aa871ec94 small fixes 2025-11-05 17:55:57 -06:00
Josh Patterson
4ffbb0bbd9 Merge remote-tracking branch 'origin/2.4/dev' into bravo 2025-11-05 15:22:11 -05:00
Jorge Reyes
f859fe6517 Merge pull request #15192 from Security-Onion-Solutions/securityonion-strelka
strelka use single master image
2025-11-05 08:07:01 -06:00
Jason Ertel
021b425b8b Merge pull request #15198 from Security-Onion-Solutions/jertel/wip
ensure previous setup outcomes are cleared
2025-11-04 16:10:53 -05:00
Jason Ertel
d95122ca01 ensure previous setup outcomes are cleared 2025-11-04 16:02:39 -05:00
Josh Patterson
81d3c7351b Merge pull request #15194 from Security-Onion-Solutions/reyesj2/ea-policy
move off of cmd.script with args \
2025-11-03 17:16:35 -05:00
Josh Patterson
ccb8ffd6eb Update install_agent_grid.sls 2025-11-03 17:05:48 -05:00
reyesj2
5a8ea57a1b move off of cmd.script with args \
https://github.com/saltstack/salt/issues/68298
2025-11-03 15:31:14 -06:00
Josh Patterson
60228ec6e6 Merge pull request #15193 from Security-Onion-Solutions/salt300616
Salt 3006.16
2025-11-03 16:02:25 -05:00
reyesj2
635545630b strelka use single master image 2025-11-03 09:36:46 -06:00
Mike Reeves
df8afda999 Merge pull request #15188 from Security-Onion-Solutions/cogburn/multiple-models
Available Models
2025-11-03 09:39:16 -05:00
Corey Ogburn
f80b090c93 Update limits 2025-10-31 14:48:30 -06:00
Corey Ogburn
806173f7e3 Available Models
Utilizes Jason's new Array of Objects UI.
2025-10-31 14:07:30 -06:00
Josh Patterson
2f6c1b82a6 Merge pull request #15185 from Security-Onion-Solutions/salt300616
Upgrade Salt 3006.16
2025-10-31 09:47:01 -04:00
Josh Patterson
92be8df95d Merge pull request #15122 from Security-Onion-Solutions/amv
nsm virtual disk and new nsm_total grain
2025-10-08 14:15:51 -04:00
Josh Patterson
b1acbf3114 Merge pull request #15098 from Security-Onion-Solutions/byoh
Byoh
2025-10-01 15:06:01 -04:00
Josh Patterson
8043e09ec1 Merge pull request #15076 from Security-Onion-Solutions/vlb2
Vlb2
2025-09-26 15:44:53 -04:00
Josh Patterson
25c746bb14 Merge pull request #15067 from Security-Onion-Solutions/vlb2
Vlb2
2025-09-25 16:12:52 -04:00
Josh Patterson
a91e8b26f6 Merge pull request #15066 from Security-Onion-Solutions/vlb2
set interface for network.ip_addrs for hypervisors
2025-09-24 16:51:07 -04:00
Josh Patterson
e826ea5d04 Merge pull request #15065 from Security-Onion-Solutions/vlb2
update service file, use salt.minion state to update mine_functions
2025-09-24 15:20:31 -04:00
Josh Patterson
23a9780ebb Merge pull request #15061 from Security-Onion-Solutions/vlb2
only update mine for managerhype during setup
2025-09-23 15:56:47 -04:00
Josh Patterson
9cb8ebbaa7 Merge pull request #15056 from Security-Onion-Solutions/vlb2
Vlb2
2025-09-23 09:05:55 -04:00
Josh Patterson
03892bad5e Merge pull request #15015 from Security-Onion-Solutions/vlb2
Vlb2
2025-09-10 14:58:41 -04:00
Josh Patterson
77fef02116 Merge pull request #14994 from Security-Onion-Solutions/vlb2
pass pillar properly
2025-09-04 11:06:31 -04:00
Josh Patterson
f3328c41fb Merge pull request #14990 from Security-Onion-Solutions/vlb2
merge with 2.4/dev
2025-09-03 10:37:46 -04:00
Josh Patterson
23ae259c82 Merge pull request #14972 from Security-Onion-Solutions/vlb2
Vlb2
2025-08-28 10:41:23 -04:00
Josh Patterson
45f25ca62d Merge pull request #14966 from Security-Onion-Solutions/vlb2
managerhype
2025-08-26 15:07:36 -04:00
58 changed files with 2054 additions and 496 deletions

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

@@ -172,7 +172,15 @@ MANAGER_HOSTNAME = socket.gethostname()
def _download_image():
"""
Download and validate the Oracle Linux KVM 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
Returns:
bool: True if successful or file exists with valid checksum, False on error
"""
@@ -186,44 +194,106 @@ def _download_image():
log.info("Starting image download process")
try:
# Download file
log.info("Downloading Oracle Linux KVM image from %s to %s", IMAGE_URL, IMAGE_PATH)
response = requests.get(IMAGE_URL, stream=True)
response.raise_for_status()
# 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
# Get total file size for progress tracking
total_size = int(response.headers.get('content-length', 0))
downloaded_size = 0
last_log_time = 0
for attempt in range(1, max_attempts + 1):
log.info("Download attempt %d of %d", attempt, max_attempts)
# Save file with progress logging
with salt.utils.files.fopen(IMAGE_PATH, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
downloaded_size += len(chunk)
try:
# Download file with timeouts
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.raise_for_status()
# 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)",
progress, downloaded_size, total_size)
last_log_time = current_time
# 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
# Validate downloaded file
if not _validate_image_checksum(IMAGE_PATH, IMAGE_SHA256):
os.unlink(IMAGE_PATH)
return False
# Save file with progress logging and stall detection
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()
log.info("Successfully downloaded and validated Oracle Linux KVM image")
return True
# 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"
)
except Exception as e:
log.error("Error downloading hypervisor image: %s", str(e))
if os.path.exists(IMAGE_PATH):
os.unlink(IMAGE_PATH)
return False
# Log progress every second
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)",
progress, downloaded_size, total_size)
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))
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():
"""
@@ -419,25 +489,28 @@ 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():
def _apply_dyanno_hypervisor_state(status):
"""
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("Applying soc.dyanno.hypervisor state on salt master")
log.info(f"Applying soc.dyanno.hypervisor state on salt master with status: {status}")
# 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', "pillar={'baseDomain': {'status': 'PreInit'}}", 'concurrent=True'], tgt_type='glob')
state_result = local.cmd(target, 'state.apply', ['soc.dyanno.hypervisor', f"pillar={{'baseDomain': {{'status': '{status}'}}}}", 'concurrent=True'], tgt_type='glob')
log.debug(f"state_result: {state_result}")
# Check if state was applied successfully
if state_result:
@@ -454,17 +527,17 @@ def _apply_dyanno_hypervisor_state():
success = False
if success:
log.info("Successfully applied soc.dyanno.hypervisor state")
log.info(f"Successfully applied soc.dyanno.hypervisor state with status: {status}")
return True
else:
log.error("Failed to apply soc.dyanno.hypervisor state")
log.error(f"Failed to apply soc.dyanno.hypervisor state with status: {status}")
return False
else:
log.error("No response from salt master when applying soc.dyanno.hypervisor state")
log.error(f"No response from salt master when applying soc.dyanno.hypervisor state with status: {status}")
return False
except Exception as e:
log.error(f"Error applying soc.dyanno.hypervisor state: {str(e)}")
log.error(f"Error applying soc.dyanno.hypervisor state with status: {status}: {str(e)}")
return False
def _apply_cloud_config_state():
@@ -598,11 +671,6 @@ 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
@@ -616,9 +684,12 @@ 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',
@@ -631,6 +702,8 @@ 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',
@@ -655,6 +728,12 @@ 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,4 +1,7 @@
{% 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 %}
@@ -8,3 +11,11 @@
{{ 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 Stenographer.
description: List of BPF filters to apply to the PCAP engine.
multiline: True
forcedType: "[]string"
helpLink: bpf.html
suricata:
description: List of BPF filters to apply to 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.
multiline: True
forcedType: "[]string"
helpLink: bpf.html

View File

@@ -1,7 +1,16 @@
{% 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,7 +1,16 @@
{% 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

@@ -29,9 +29,26 @@ fi
interface="$1"
shift
tcpdump -i $interface -ddd $@ | tail -n+2 |
while read line; do
# 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
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

@@ -395,7 +395,7 @@ is_manager_node() {
}
is_sensor_node() {
# Check to see if this is a sensor (forward) node
# Check to see if this is a sensor node
is_single_node_grid && return 0
grep "role: so-" /etc/salt/grains | grep -E "sensor|heavynode" &> /dev/null
}

View File

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

View File

@@ -0,0 +1,34 @@
{# 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 'elasticfleet/map.jinja' import ELASTICFLEETMERGED %}
{# advanced config_yaml options for elasticfleet logstash output #}
{% set ADV_OUTPUT_LOGSTASH_RAW = ELASTICFLEETMERGED.config.outputs.logstash %}
{% set ADV_OUTPUT_LOGSTASH = {} %}
{% for k, v in ADV_OUTPUT_LOGSTASH_RAW.items() %}
{% if v != "" and v is not none %}
{% if k == 'queue_mem_events' %}
{# rename queue_mem_events queue.mem.events #}
{% do ADV_OUTPUT_LOGSTASH.update({'queue.mem.events':v}) %}
{% elif k == 'loadbalance' %}
{% if v %}
{# only include loadbalance config when its True #}
{% do ADV_OUTPUT_LOGSTASH.update({k:v}) %}
{% endif %}
{% else %}
{% do ADV_OUTPUT_LOGSTASH.update({k:v}) %}
{% endif %}
{% endif %}
{% endfor %}
{% set LOGSTASH_CONFIG_YAML_RAW = [] %}
{% if ADV_OUTPUT_LOGSTASH %}
{% for k, v in ADV_OUTPUT_LOGSTASH.items() %}
{% do LOGSTASH_CONFIG_YAML_RAW.append(k ~ ': ' ~ v) %}
{% endfor %}
{% endif %}
{% set LOGSTASH_CONFIG_YAML = LOGSTASH_CONFIG_YAML_RAW | join('\\n') if LOGSTASH_CONFIG_YAML_RAW else '' %}

View File

@@ -10,12 +10,19 @@ elasticfleet:
grid_enrollment: ''
defend_filters:
enable_auto_configuration: False
outputs:
logstash:
bulk_max_size: ''
worker: ''
queue_mem_events: ''
timeout: ''
loadbalance: False
compression_level: ''
subscription_integrations: False
auto_upgrade_integrations: False
logging:
zeek:
excluded:
- analyzer
- broker
- capture_loss
- cluster

View File

@@ -32,6 +32,17 @@ so-elastic-fleet-auto-configure-logstash-outputs:
- retry:
attempts: 4
interval: 30
{# Separate from above in order to catch elasticfleet-logstash.crt changes and force update to fleet output policy #}
so-elastic-fleet-auto-configure-logstash-outputs-force:
cmd.run:
- name: /usr/sbin/so-elastic-fleet-outputs-update --certs
- retry:
attempts: 4
interval: 30
- onchanges:
- x509: etc_elasticfleet_logstash_crt
- x509: elasticfleet_kafka_crt
{% endif %}
# If enabled, automatically update Fleet Server URLs & ES Connection

View File

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

View File

@@ -121,6 +121,9 @@
"phases": {
"cold": {
"actions": {
"allocate":{
"number_of_replicas": ""
},
"set_priority": {"priority": 0}
},
"min_age": "60d"
@@ -137,12 +140,31 @@
"max_age": "30d",
"max_primary_shard_size": "50gb"
},
"forcemerge":{
"max_num_segments": ""
},
"shrink":{
"max_primary_shard_size": "",
"method": "COUNT",
"number_of_shards": ""
},
"set_priority": {"priority": 100}
},
"min_age": "0ms"
},
"warm": {
"actions": {
"allocate": {
"number_of_replicas": ""
},
"forcemerge": {
"max_num_segments": ""
},
"shrink":{
"max_primary_shard_size": "",
"method": "COUNT",
"number_of_shards": ""
},
"set_priority": {"priority": 50}
},
"min_age": "30d"

View File

@@ -50,6 +50,46 @@ elasticfleet:
global: True
forcedType: bool
helpLink: elastic-fleet.html
outputs:
logstash:
bulk_max_size:
description: The maximum number of events to bulk in a single Logstash request.
global: True
forcedType: int
advanced: True
helpLink: elastic-fleet.html
worker:
description: The number of workers per configured host publishing events.
global: True
forcedType: int
advanced: true
helpLink: elastic-fleet.html
queue_mem_events:
title: queued events
description: The number of events the queue can store. This value should be evenly divisible by the smaller of 'bulk_max_size' to avoid sending partial batches to the output.
global: True
forcedType: int
advanced: True
helpLink: elastic-fleet.html
timeout:
description: The number of seconds to wait for responses from the Logstash server before timing out. Eg 30s
regex: ^[0-9]+s$
advanced: True
global: True
helpLink: elastic-fleet.html
loadbalance:
description: If true and multiple Logstash hosts are configured, the output plugin load balances published events onto all Logstash hosts. If false, the output plugin sends all events to one host (determined at random) and switches to another host if the selected one becomes unresponsive.
forcedType: bool
advanced: True
global: True
helpLink: elastic-fleet.html
compression_level:
description: The gzip compression level. The compression level must be in the range of 1 (best speed) to 9 (best compression).
regex: ^[1-9]$
forcedType: int
advanced: True
global: True
helpLink: elastic-fleet.html
server:
custom_fqdn:
description: Custom FQDN for Agents to connect to. One per line.

View File

@@ -3,11 +3,36 @@
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
# or more contributor license agreements. Licensed under the Elastic License 2.0; you may not use
# this file except in compliance with the Elastic License 2.0.
{% from 'vars/globals.map.jinja' import GLOBALS %}
{% from 'elasticfleet/map.jinja' import ELASTICFLEETMERGED %}
{%- from 'vars/globals.map.jinja' import GLOBALS %}
{%- from 'elasticfleet/map.jinja' import ELASTICFLEETMERGED %}
{%- from 'elasticfleet/config.map.jinja' import LOGSTASH_CONFIG_YAML %}
. /usr/sbin/so-common
FORCE_UPDATE=false
UPDATE_CERTS=false
LOGSTASH_PILLAR_CONFIG_YAML="{{ LOGSTASH_CONFIG_YAML }}"
LOGSTASH_PILLAR_STATE_FILE="/opt/so/state/esfleet_logstash_config_pillar"
while [[ $# -gt 0 ]]; do
case $1 in
-f|--force)
FORCE_UPDATE=true
shift
;;
-c| --certs)
UPDATE_CERTS=true
FORCE_UPDATE=true
shift
;;
*)
echo "Unknown option $1"
echo "Usage: $0 [-f|--force] [-c|--certs]"
exit 1
;;
esac
done
# Only run on Managers
if ! is_manager_node; then
printf "Not a Manager Node... Exiting"
@@ -17,17 +42,49 @@ 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')
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)
# Revert escaped \\n to \n for jq
LOGSTASH_PILLAR_CONFIG_YAML=$(printf '%b' "$LOGSTASH_PILLAR_CONFIG_YAML")
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}')
if [[ "$UPDATE_CERTS" != "true" ]]; then
# Reuse existing secret
JSON_STRING=$(jq -n \
--arg UPDATEDLIST "$NEW_LIST_JSON" \
--arg CONFIG_YAML "$LOGSTASH_PILLAR_CONFIG_YAML" \
--argjson SECRETS "$SECRETS" \
--argjson SSL_CONFIG "$SSL_CONFIG" \
'{"name":"grid-logstash","type":"logstash","hosts": $UPDATEDLIST,"is_default":true,"is_default_monitoring":true,"config_yaml":$CONFIG_YAML,"ssl": $SSL_CONFIG,"secrets": $SECRETS}')
else
# Update certs, creating new secret
JSON_STRING=$(jq -n \
--arg UPDATEDLIST "$NEW_LIST_JSON" \
--arg CONFIG_YAML "$LOGSTASH_PILLAR_CONFIG_YAML" \
--arg LOGSTASHKEY "$LOGSTASHKEY" \
--arg LOGSTASHCRT "$LOGSTASHCRT" \
--arg LOGSTASHCA "$LOGSTASHCA" \
'{"name":"grid-logstash","type":"logstash","hosts": $UPDATEDLIST,"is_default":true,"is_default_monitoring":true,"config_yaml":$CONFIG_YAML,"ssl": {"certificate": $LOGSTASHCRT,"certificate_authorities":[ $LOGSTASHCA ]},"secrets": {"ssl":{"key": $LOGSTASHKEY }}}')
fi
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}')
if [[ "$UPDATE_CERTS" != "true" ]]; then
# Reuse existing ssl config
JSON_STRING=$(jq -n \
--arg UPDATEDLIST "$NEW_LIST_JSON" \
--arg CONFIG_YAML "$LOGSTASH_PILLAR_CONFIG_YAML" \
--argjson SSL_CONFIG "$SSL_CONFIG" \
'{"name":"grid-logstash","type":"logstash","hosts": $UPDATEDLIST,"is_default":true,"is_default_monitoring":true,"config_yaml":$CONFIG_YAML,"ssl": $SSL_CONFIG}')
else
# Update ssl config
JSON_STRING=$(jq -n \
--arg UPDATEDLIST "$NEW_LIST_JSON" \
--arg CONFIG_YAML "$LOGSTASH_PILLAR_CONFIG_YAML" \
--arg LOGSTASHKEY "$LOGSTASHKEY" \
--arg LOGSTASHCRT "$LOGSTASHCRT" \
--arg LOGSTASHCA "$LOGSTASHCA" \
'{"name":"grid-logstash","type":"logstash","hosts": $UPDATEDLIST,"is_default":true,"is_default_monitoring":true,"config_yaml":$CONFIG_YAML,"ssl": {"certificate": $LOGSTASHCRT,"key": $LOGSTASHKEY,"certificate_authorities":[ $LOGSTASHCA ]}}')
fi
fi
fi
@@ -38,19 +95,42 @@ function update_kafka_outputs() {
# Make sure SSL configuration is included in policy updates for Kafka output. SSL is configured in so-elastic-fleet-setup
if kafka_policy=$(curl -K /opt/so/conf/elasticsearch/curl.config -L "http://localhost:5601/api/fleet/outputs/so-manager_kafka" --fail 2>/dev/null); then
SSL_CONFIG=$(echo "$kafka_policy" | jq -r '.item.ssl')
KAFKAKEY=$(openssl rsa -in /etc/pki/elasticfleet-kafka.key)
KAFKACRT=$(openssl x509 -in /etc/pki/elasticfleet-kafka.crt)
KAFKACA=$(openssl x509 -in /etc/pki/tls/certs/intca.crt)
if SECRETS=$(echo "$kafka_policy" | jq -er '.item.secrets' 2>/dev/null); then
# Update policy when fleet has secrets enabled
JSON_STRING=$(jq -n \
--arg UPDATEDLIST "$NEW_LIST_JSON" \
--argjson SSL_CONFIG "$SSL_CONFIG" \
--argjson SECRETS "$SECRETS" \
'{"name": "grid-kafka","type": "kafka","hosts": $UPDATEDLIST,"is_default": true,"is_default_monitoring": true,"config_yaml": "","ssl": $SSL_CONFIG,"secrets": $SECRETS}')
if [[ "$UPDATE_CERTS" != "true" ]]; then
# Update policy when fleet has secrets enabled
JSON_STRING=$(jq -n \
--arg UPDATEDLIST "$NEW_LIST_JSON" \
--argjson SSL_CONFIG "$SSL_CONFIG" \
--argjson SECRETS "$SECRETS" \
'{"name": "grid-kafka","type": "kafka","hosts": $UPDATEDLIST,"is_default": true,"is_default_monitoring": true,"config_yaml": "","ssl": $SSL_CONFIG,"secrets": $SECRETS}')
else
# Update certs, creating new secret
JSON_STRING=$(jq -n \
--arg UPDATEDLIST "$NEW_LIST_JSON" \
--arg KAFKAKEY "$KAFKAKEY" \
--arg KAFKACRT "$KAFKACRT" \
--arg KAFKACA "$KAFKACA" \
'{"name": "grid-kafka","type": "kafka","hosts": $UPDATEDLIST,"is_default": true,"is_default_monitoring": true,"config_yaml": "","ssl": {"certificate_authorities":[ $KAFKACA ],"certificate": $KAFKACRT ,"key":"","verification_mode":"full"},"secrets": {"ssl":{"key": $KAFKAKEY }}}')
fi
else
# Update policy when fleet has secrets disabled or policy hasn't been force updated
JSON_STRING=$(jq -n \
--arg UPDATEDLIST "$NEW_LIST_JSON" \
--argjson SSL_CONFIG "$SSL_CONFIG" \
'{"name": "grid-kafka","type": "kafka","hosts": $UPDATEDLIST,"is_default": true,"is_default_monitoring": true,"config_yaml": "","ssl": $SSL_CONFIG}')
if [[ "$UPDATE_CERTS" != "true" ]]; then
# Update policy when fleet has secrets disabled or policy hasn't been force updated
JSON_STRING=$(jq -n \
--arg UPDATEDLIST "$NEW_LIST_JSON" \
--argjson SSL_CONFIG "$SSL_CONFIG" \
'{"name": "grid-kafka","type": "kafka","hosts": $UPDATEDLIST,"is_default": true,"is_default_monitoring": true,"config_yaml": "","ssl": $SSL_CONFIG}')
else
# Update ssl config
JSON_STRING=$(jq -n \
--arg UPDATEDLIST "$NEW_LIST_JSON" \
--arg KAFKAKEY "$KAFKAKEY" \
--arg KAFKACRT "$KAFKACRT" \
--arg KAFKACA "$KAFKACA" \
'{"name": "grid-kafka","type": "kafka","hosts": $UPDATEDLIST,"is_default": true,"is_default_monitoring": true,"config_yaml": "","ssl": { "certificate_authorities": [ $KAFKACA ], "certificate": $KAFKACRT, "key": $KAFKAKEY, "verification_mode": "full" }}')
fi
fi
# Update Kafka outputs
curl -K /opt/so/conf/elasticsearch/curl.config -L -X PUT "localhost:5601/api/fleet/outputs/so-manager_kafka" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d "$JSON_STRING" | jq
@@ -73,7 +153,7 @@ function update_kafka_outputs() {
# Get the current list of kafka outputs & hash them
CURRENT_LIST=$(jq -c -r '.item.hosts' <<< "$RAW_JSON")
CURRENT_HASH=$(sha1sum <<< "$CURRENT_LIST" | awk '{print $1}')
CURRENT_HASH=$(sha256sum <<< "$CURRENT_LIST" | awk '{print $1}')
declare -a NEW_LIST=()
@@ -96,10 +176,19 @@ function update_kafka_outputs() {
printf "Failed to query for current Logstash Outputs..."
exit 1
fi
# logstash adv config - compare pillar to last state file value
if [[ -f "$LOGSTASH_PILLAR_STATE_FILE" ]]; then
PREVIOUS_LOGSTASH_PILLAR_CONFIG_YAML=$(cat "$LOGSTASH_PILLAR_STATE_FILE")
if [[ "$LOGSTASH_PILLAR_CONFIG_YAML" != "$PREVIOUS_LOGSTASH_PILLAR_CONFIG_YAML" ]]; then
echo "Logstash pillar config has changed - forcing update"
FORCE_UPDATE=true
fi
echo "$LOGSTASH_PILLAR_CONFIG_YAML" > "$LOGSTASH_PILLAR_STATE_FILE"
fi
# Get the current list of Logstash outputs & hash them
CURRENT_LIST=$(jq -c -r '.item.hosts' <<< "$RAW_JSON")
CURRENT_HASH=$(sha1sum <<< "$CURRENT_LIST" | awk '{print $1}')
CURRENT_HASH=$(sha256sum <<< "$CURRENT_LIST" | awk '{print $1}')
declare -a NEW_LIST=()
@@ -148,10 +237,10 @@ function update_kafka_outputs() {
# Sort & hash the new list of Logstash Outputs
NEW_LIST_JSON=$(jq --compact-output --null-input '$ARGS.positional' --args -- "${NEW_LIST[@]}")
NEW_HASH=$(sha1sum <<< "$NEW_LIST_JSON" | awk '{print $1}')
NEW_HASH=$(sha256sum <<< "$NEW_LIST_JSON" | awk '{print $1}')
# Compare the current & new list of outputs - if different, update the Logstash outputs
if [ "$NEW_HASH" = "$CURRENT_HASH" ]; then
if [[ "$NEW_HASH" = "$CURRENT_HASH" ]] && [[ "$FORCE_UPDATE" != "true" ]]; then
printf "\nHashes match - no update needed.\n"
printf "Current List: $CURRENT_LIST\nNew List: $NEW_LIST_JSON\n"

View File

@@ -72,6 +72,8 @@ elasticsearch:
actions:
set_priority:
priority: 0
allocate:
number_of_replicas: ""
min_age: 60d
delete:
actions:
@@ -84,11 +86,25 @@ elasticsearch:
max_primary_shard_size: 50gb
set_priority:
priority: 100
forcemerge:
max_num_segments: ""
shrink:
max_primary_shard_size: ""
method: COUNT
number_of_shards: ""
min_age: 0ms
warm:
actions:
set_priority:
priority: 50
forcemerge:
max_num_segments: ""
shrink:
max_primary_shard_size: ""
method: COUNT
number_of_shards: ""
allocate:
number_of_replicas: ""
min_age: 30d
so-case:
index_sorting: false
@@ -245,7 +261,6 @@ elasticsearch:
set_priority:
priority: 50
min_age: 30d
warm: 7
so-detection:
index_sorting: false
index_template:
@@ -584,7 +599,6 @@ elasticsearch:
set_priority:
priority: 50
min_age: 30d
warm: 7
so-import:
index_sorting: false
index_template:
@@ -932,7 +946,6 @@ elasticsearch:
set_priority:
priority: 50
min_age: 30d
warm: 7
so-hydra:
close: 30
delete: 365
@@ -1043,7 +1056,6 @@ elasticsearch:
set_priority:
priority: 50
min_age: 30d
warm: 7
so-lists:
index_sorting: false
index_template:
@@ -1127,6 +1139,8 @@ elasticsearch:
actions:
set_priority:
priority: 0
allocate:
number_of_replicas: ""
min_age: 60d
delete:
actions:
@@ -1139,11 +1153,25 @@ elasticsearch:
max_primary_shard_size: 50gb
set_priority:
priority: 100
forcemerge:
max_num_segments: ""
shrink:
max_primary_shard_size: ""
method: COUNT
number_of_shards: ""
min_age: 0ms
warm:
actions:
set_priority:
priority: 50
allocate:
number_of_replicas: ""
forcemerge:
max_num_segments: ""
shrink:
max_primary_shard_size: ""
method: COUNT
number_of_shards: ""
min_age: 30d
so-logs-detections_x_alerts:
index_sorting: false
@@ -3123,7 +3151,6 @@ elasticsearch:
set_priority:
priority: 50
min_age: 30d
warm: 7
so-logs-system_x_application:
index_sorting: false
index_template:

View File

@@ -1,15 +1,79 @@
{
"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" } }
]
"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"
}
}
]
}

View File

@@ -1,30 +1,155 @@
{
"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
}
},
{ "pipeline": { "if": "ctx?.event?.dataset != null", "name": "suricata.{{event.dataset}}" } }
]
"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}}"
}
}
]
}

View File

@@ -1,21 +1,136 @@
{
"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" } }
]
"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"
}
}
]
}

View File

@@ -0,0 +1,56 @@
{
"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

@@ -0,0 +1,52 @@
{
"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

@@ -0,0 +1,61 @@
{
"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,35 +1,227 @@
{
"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" } }
]
"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"
}
}
]
}

View File

@@ -1,20 +0,0 @@
{
"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

@@ -131,6 +131,47 @@ elasticsearch:
description: Maximum primary shard size. Once an index reaches this limit, it will be rolled over into a new index.
global: True
helpLink: elasticsearch.html
shrink:
method:
description: Shrink the index to a new index with fewer primary shards. Shrink operation is by count or size.
options:
- COUNT
- SIZE
global: True
advanced: True
forcedType: string
number_of_shards:
title: shard count
description: Desired shard count. Note that this value is only used when the shrink method selected is 'COUNT'.
global: True
forcedType: int
advanced: True
max_primary_shard_size:
title: max shard size
description: Desired shard size in gb/tb/pb eg. 100gb. Note that this value is only used when the shrink method selected is 'SIZE'.
regex: ^[0-9]+(?:gb|tb|pb)$
global: True
forcedType: string
advanced: True
allow_write_after_shrink:
description: Allow writes after shrink.
global: True
forcedType: bool
default: False
advanced: True
forcemerge:
max_num_segments:
description: Reduce the number of segments in each index shard and clean up deleted documents.
global: True
forcedType: int
advanced: True
index_codec:
title: compression
description: Use higher compression for stored fields at the cost of slower performance.
forcedType: bool
global: True
default: False
advanced: True
cold:
min_age:
description: Minimum age of index. ex. 60d - This determines when the index should be moved to the cold tier. While still searchable, this tier is typically optimized for lower storage costs rather than search speed. Its important to note that this is calculated relative to the rollover date (NOT the original creation date of the index). For example, if you have an index that is set to rollover after 30 days and cold min_age set to 60 then there will be 30 days from index creation to rollover and then an additional 60 days before moving to cold tier.
@@ -144,6 +185,12 @@ elasticsearch:
description: Used for index recovery after a node restart. Indices with higher priorities are recovered before indices with lower priorities.
global: True
helpLink: elasticsearch.html
allocate:
number_of_replicas:
description: Set the number of replicas. Remains the same as the previous phase by default.
forcedType: int
global: True
advanced: True
warm:
min_age:
description: Minimum age of index. ex. 30d - This determines when the index should be moved to the warm tier. Nodes in the warm tier generally dont need to be as fast as those in the hot tier. Its important to note that this is calculated relative to the rollover date (NOT the original creation date of the index). For example, if you have an index that is set to rollover after 30 days and warm min_age set to 30 then there will be 30 days from index creation to rollover and then an additional 30 days before moving to warm tier.
@@ -158,6 +205,52 @@ elasticsearch:
forcedType: int
global: True
helpLink: elasticsearch.html
shrink:
method:
description: Shrink the index to a new index with fewer primary shards. Shrink operation is by count or size.
options:
- COUNT
- SIZE
global: True
advanced: True
number_of_shards:
title: shard count
description: Desired shard count. Note that this value is only used when the shrink method selected is 'COUNT'.
global: True
forcedType: int
advanced: True
max_primary_shard_size:
title: max shard size
description: Desired shard size in gb/tb/pb eg. 100gb. Note that this value is only used when the shrink method selected is 'SIZE'.
regex: ^[0-9]+(?:gb|tb|pb)$
global: True
forcedType: string
advanced: True
allow_write_after_shrink:
description: Allow writes after shrink.
global: True
forcedType: bool
default: False
advanced: True
forcemerge:
max_num_segments:
description: Reduce the number of segments in each index shard and clean up deleted documents.
global: True
forcedType: int
advanced: True
index_codec:
title: compression
description: Use higher compression for stored fields at the cost of slower performance.
forcedType: bool
global: True
default: False
advanced: True
allocate:
number_of_replicas:
description: Set the number of replicas. Remains the same as the previous phase by default.
forcedType: int
global: True
advanced: True
delete:
min_age:
description: Minimum age of index. ex. 90d - This determines when the index should be deleted. Its important to note that this is calculated relative to the rollover date (NOT the original creation date of the index). For example, if you have an index that is set to rollover after 30 days and delete min_age set to 90 then there will be 30 days from index creation to rollover and then an additional 90 days before deletion.
@@ -287,6 +380,47 @@ elasticsearch:
global: True
advanced: True
helpLink: elasticsearch.html
shrink:
method:
description: Shrink the index to a new index with fewer primary shards. Shrink operation is by count or size.
options:
- COUNT
- SIZE
global: True
advanced: True
forcedType: string
number_of_shards:
title: shard count
description: Desired shard count. Note that this value is only used when the shrink method selected is 'COUNT'.
global: True
forcedType: int
advanced: True
max_primary_shard_size:
title: max shard size
description: Desired shard size in gb/tb/pb eg. 100gb. Note that this value is only used when the shrink method selected is 'SIZE'.
regex: ^[0-9]+(?:gb|tb|pb)$
global: True
forcedType: string
advanced: True
allow_write_after_shrink:
description: Allow writes after shrink.
global: True
forcedType: bool
default: False
advanced: True
forcemerge:
max_num_segments:
description: Reduce the number of segments in each index shard and clean up deleted documents.
global: True
forcedType: int
advanced: True
index_codec:
title: compression
description: Use higher compression for stored fields at the cost of slower performance.
forcedType: bool
global: True
default: False
advanced: True
warm:
min_age:
description: Minimum age of index. ex. 30d - This determines when the index should be moved to the warm tier. Nodes in the warm tier generally dont need to be as fast as those in the hot tier. Its important to note that this is calculated relative to the rollover date (NOT the original creation date of the index). For example, if you have an index that is set to rollover after 30 days and warm min_age set to 30 then there will be 30 days from index creation to rollover and then an additional 30 days before moving to warm tier.
@@ -314,6 +448,52 @@ elasticsearch:
global: True
advanced: True
helpLink: elasticsearch.html
shrink:
method:
description: Shrink the index to a new index with fewer primary shards. Shrink operation is by count or size.
options:
- COUNT
- SIZE
global: True
advanced: True
number_of_shards:
title: shard count
description: Desired shard count. Note that this value is only used when the shrink method selected is 'COUNT'.
global: True
forcedType: int
advanced: True
max_primary_shard_size:
title: max shard size
description: Desired shard size in gb/tb/pb eg. 100gb. Note that this value is only used when the shrink method selected is 'SIZE'.
regex: ^[0-9]+(?:gb|tb|pb)$
global: True
forcedType: string
advanced: True
allow_write_after_shrink:
description: Allow writes after shrink.
global: True
forcedType: bool
default: False
advanced: True
forcemerge:
max_num_segments:
description: Reduce the number of segments in each index shard and clean up deleted documents.
global: True
forcedType: int
advanced: True
index_codec:
title: compression
description: Use higher compression for stored fields at the cost of slower performance.
forcedType: bool
global: True
default: False
advanced: True
allocate:
number_of_replicas:
description: Set the number of replicas. Remains the same as the previous phase by default.
forcedType: int
global: True
advanced: True
cold:
min_age:
description: Minimum age of index. ex. 60d - This determines when the index should be moved to the cold tier. While still searchable, this tier is typically optimized for lower storage costs rather than search speed. Its important to note that this is calculated relative to the rollover date (NOT the original creation date of the index). For example, if you have an index that is set to rollover after 30 days and cold min_age set to 60 then there will be 30 days from index creation to rollover and then an additional 60 days before moving to cold tier.
@@ -330,6 +510,12 @@ elasticsearch:
global: True
advanced: True
helpLink: elasticsearch.html
allocate:
number_of_replicas:
description: Set the number of replicas. Remains the same as the previous phase by default.
forcedType: int
global: True
advanced: True
delete:
min_age:
description: Minimum age of index. ex. 90d - This determines when the index should be deleted. Its important to note that this is calculated relative to the rollover date (NOT the original creation date of the index). For example, if you have an index that is set to rollover after 30 days and delete min_age set to 90 then there will be 30 days from index creation to rollover and then an additional 90 days before deletion.

View File

@@ -61,5 +61,55 @@
{% do settings.index_template.template.settings.index.pop('sort') %}
{% endif %}
{% endif %}
{# advanced ilm actions #}
{% if settings.policy is defined and settings.policy.phases is defined %}
{% set PHASE_NAMES = ["hot", "warm", "cold"] %}
{% for P in PHASE_NAMES %}
{% if settings.policy.phases[P] is defined and settings.policy.phases[P].actions is defined %}
{% set PHASE = settings.policy.phases[P].actions %}
{# remove allocate action if number_of_replicas isn't configured #}
{% if PHASE.allocate is defined %}
{% if PHASE.allocate.number_of_replicas is not defined or PHASE.allocate.number_of_replicas == "" %}
{% do PHASE.pop('allocate', none) %}
{% endif %}
{% endif %}
{# start shrink action #}
{% if PHASE.shrink is defined %}
{% if PHASE.shrink.method is defined %}
{% if PHASE.shrink.method == 'COUNT' and PHASE.shrink.number_of_shards is defined and PHASE.shrink.number_of_shards %}
{# remove max_primary_shard_size value when doing shrink operation by count vs size #}
{% do PHASE.shrink.pop('max_primary_shard_size', none) %}
{% elif PHASE.shrink.method == 'SIZE' and PHASE.shrink.max_primary_shard_size is defined and PHASE.shrink.max_primary_shard_size %}
{# remove number_of_shards value when doing shrink operation by size vs count #}
{% do PHASE.shrink.pop('number_of_shards', none) %}
{% else %}
{# method isn't defined or missing a required config number_of_shards/max_primary_shard_size #}
{% do PHASE.pop('shrink', none) %}
{% endif %}
{% endif %}
{% endif %}
{# always remove shrink method since its only used for SOC config, not in the actual ilm policy #}
{% if PHASE.shrink is defined %}
{% do PHASE.shrink.pop('method', none) %}
{% endif %}
{# end shrink action #}
{# start force merge #}
{% if PHASE.forcemerge is defined %}
{% if PHASE.forcemerge.index_codec is defined and PHASE.forcemerge.index_codec %}
{% do PHASE.forcemerge.update({'index_codec': 'best_compression'}) %}
{% else %}
{% do PHASE.forcemerge.pop('index_codec', none) %}
{% endif %}
{% if PHASE.forcemerge.max_num_segments is not defined or not PHASE.forcemerge.max_num_segments %}
{# max_num_segments is empty, drop it #}
{% do PHASE.pop('forcemerge', none) %}
{% endif %}
{% endif %}
{# end force merge #}
{% endif %}
{% endfor %}
{% endif %}
{% do ES_INDEX_SETTINGS.update({index | replace("_x_", "."): ES_INDEX_SETTINGS_GLOBAL_OVERRIDES[index]}) %}
{% endfor %}

View File

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

View File

@@ -41,13 +41,13 @@ create_temp_file() {
}
log_title() {
if [ $1 == "LOG" ]; then
if [ "$1" == "LOG" ]; then
echo -e "\n${BOLD}================ $2 ================${NC}\n"
elif [ $1 == "OK" ]; then
elif [ "$1" == "OK" ]; then
echo -e "${GREEN} $2 ${NC}"
elif [ $1 == "WARN" ]; then
elif [ "$1" == "WARN" ]; then
echo -e "${YELLOW} $2 ${NC}"
elif [ $1 == "ERROR" ]; then
elif [ "$1" == "ERROR" ]; then
echo -e "${RED} $2 ${NC}"
fi
}
@@ -206,7 +206,7 @@ fail() {
exit 1
}
echo -e "\nDISCLAIMER: Script output is based on current data patterns, but are approximations soley intended to assist with getting a general ILM policy configured."
echo -e "\nDISCLAIMER: Script output is based on current data patterns, but are approximations solely intended to assist with getting a general ILM policy configured."
ORG_ID=$(lookup_org_id)
[ -n "$ORG_ID" ] || fail "Unable to resolve InfluxDB org id"
@@ -756,7 +756,7 @@ if [ "$should_trigger_recommendations" = true ]; then
ilm_output=$(so-elasticsearch-query "${index}/_ilm/explain" --fail 2>/dev/null) || true
if [ -n "$ilm_output" ]; then
policy=$(echo "$ilm_output" | jq -r ".indices.\"$index\".policy // empty" 2>/dev/null)
policy=$(echo "$ilm_output" | jq -r '.indices | to_entries | .[0].value.policy // empty' 2>/dev/null)
fi
if [ -n "$policy" ] && [ -n "${policy_ages[$policy]:-}" ]; then
delete_min_age=${policy_ages[$policy]}
@@ -1024,8 +1024,12 @@ else
if [ "$ilm_indices_immediate" -gt 0 ]; then
echo -e "${BOLD}Deleting now:${NC} $ilm_indices_immediate indices (~${ilm_delete_immediate_gb} GB, $ilm_shards_immediate shards)"
fi
if [ "$ilm_indices_7d" -gt 0 ]; then
echo -e "${BOLD}Storage to be freed (7d):${NC} $ilm_indices_7d indices (~${ilm_delete_7d_gb} GB, $ilm_shards_7d shards)"
if [ "$ilm_indices_30d" -gt 0 ]; then
if [ "$ilm_delete_scheduled_30d" -gt 0 ] && [ "$ilm_indices_scheduled_30d" -gt 0 ]; then
echo -e "${BOLD}Storage to be freed (30d):${NC} $ilm_indices_30d indices (~${ilm_delete_30d_gb} GB, $ilm_shards_30d shards)"
elif [ "$ilm_indices_7d" -gt 0 ]; then
echo -e "${BOLD}Storage to be freed (7d):${NC} $ilm_indices_7d indices (~${ilm_delete_7d_gb} GB, $ilm_shards_7d shards)"
fi
fi
log_title "LOG" "Retention Projection"
@@ -1134,8 +1138,9 @@ else
for i in "${!scheduled_indices_names[@]}"; do
sorted_indices+=("${scheduled_indices_days[$i]}|${scheduled_indices_names[$i]}|${scheduled_indices_sizes[$i]}")
done
OLD_IFS="$IFS"
IFS=$'\n' sorted_indices=($(sort -t'|' -k1 -n <<<"${sorted_indices[*]}"))
unset IFS
IFS="$OLD_IFS"
for entry in "${sorted_indices[@]}"; do
IFS='|' read -r days_until index_name size_bytes <<< "$entry"

View File

@@ -45,7 +45,7 @@ used during VM provisioning to add dedicated NSM storage volumes.
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.img`
- Volume Path: `/nsm/libvirt/volumes/vm1_sensor-nsm-<epoch_timestamp>.img`
- Device: `/dev/vdb` (virtio-blk)
- VM remains stopped after attachment
@@ -75,7 +75,8 @@ used during VM provisioning to add dedicated NSM storage volumes.
- 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.img`.
- 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`.
@@ -142,6 +143,7 @@ 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
@@ -242,10 +244,13 @@ def create_volume_file(vm_name, size_gb, logger):
Raises:
VolumeCreationError: If volume creation fails
"""
# Define volume path (directory already created in main())
volume_path = os.path.join(VOLUME_DIR, f"{vm_name}-nsm.img")
# Generate epoch timestamp for unique volume naming
epoch_timestamp = int(time.time())
# Check if volume already exists
# 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}")

View File

@@ -25,13 +25,11 @@
{% set index_settings = es.get('index_settings', {}) %}
{% set input = index_settings.get('so-logs', {}) %}
{% for k in matched_integration_names %}
{% if k not in index_settings %}
{% set _ = index_settings.update({k: input}) %}
{% endif %}
{% do index_settings.update({k: input}) %}
{% endfor %}
{% for k in addon_integration_keys %}
{% if k not in matched_integration_names and k in index_settings %}
{% set _ = index_settings.pop(k) %}
{% do index_settings.pop(k) %}
{% endif %}
{% endfor %}
{{ data }}
@@ -45,14 +43,12 @@
{% set es = data.get('elasticsearch', {}) %}
{% set index_settings = es.get('index_settings', {}) %}
{% for k in matched_integration_names %}
{% if k not in index_settings %}
{% set input = ADDON_INTEGRATION_DEFAULTS[k] %}
{% set _ = index_settings.update({k: input})%}
{% endif %}
{% set input = ADDON_INTEGRATION_DEFAULTS[k] %}
{% do index_settings.update({k: input})%}
{% endfor %}
{% for k in addon_integration_keys %}
{% if k not in matched_integration_names and k in index_settings %}
{% set _ = index_settings.pop(k) %}
{% do index_settings.pop(k) %}
{% endif %}
{% endfor %}
{{ data }}

View File

@@ -17,6 +17,7 @@ def showUsage(args):
print('Usage: {} <COMMAND> <YAML_FILE> [ARGS...]'.format(sys.argv[0]), file=sys.stderr)
print(' General commands:', file=sys.stderr)
print(' append - Append a list item to a yaml key, if it exists and is a list. Requires KEY and LISTITEM args.', file=sys.stderr)
print(' removelistitem - Remove a list item from a yaml key, if it exists and is a list. Requires KEY and LISTITEM args.', file=sys.stderr)
print(' add - Add a new key and set its value. Fails if key already exists. Requires KEY and VALUE args.', file=sys.stderr)
print(' get - Displays (to stdout) the value stored in the given key. Requires KEY arg.', file=sys.stderr)
print(' remove - Removes a yaml key, if it exists. Requires KEY arg.', file=sys.stderr)
@@ -26,8 +27,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', file=sys.stderr)
print(' LISTITEM - Item to append to a given key\'s list value', 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)
sys.exit(1)
@@ -57,8 +58,32 @@ def appendItem(content, key, listItem):
return 1
def removeListItem(content, key, listItem):
pieces = key.split(".", 1)
if len(pieces) > 1:
removeListItem(content[pieces[0]], pieces[1], listItem)
else:
try:
if not isinstance(content[key], list):
raise AttributeError("Value is not a list")
if listItem in content[key]:
content[key].remove(listItem)
except (AttributeError, TypeError):
print("The existing value for the given key is not a list. No action was taken on the file.", file=sys.stderr)
return 1
except KeyError:
print("The key provided does not exist. No action was taken on the file.", file=sys.stderr)
return 1
def convertType(value):
if isinstance(value, str) and len(value) > 0 and (not value.startswith("0") or len(value) == 1):
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 "." in value:
try:
value = float(value)
@@ -97,6 +122,23 @@ def append(args):
return 0
def removelistitem(args):
if len(args) != 3:
print('Missing filename, key arg, or list item to remove', file=sys.stderr)
showUsage(None)
return 1
filename = args[0]
key = args[1]
listItem = args[2]
content = loadYaml(filename)
removeListItem(content, key, convertType(listItem))
writeYaml(filename, content)
return 0
def addKey(content, key, value):
pieces = key.split(".", 1)
if len(pieces) > 1:
@@ -205,6 +247,7 @@ def main():
"help": showUsage,
"add": add,
"append": append,
"removelistitem": removelistitem,
"get": get,
"remove": remove,
"replace": replace,

View File

@@ -361,6 +361,29 @@ 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"
@@ -434,3 +457,126 @@ class TestRemove(unittest.TestCase):
self.assertEqual(result, 1)
self.assertIn("Missing filename or key arg", mock_stderr.getvalue())
sysmock.assert_called_once_with(1)
class TestRemoveListItem(unittest.TestCase):
def test_removelistitem_missing_arg(self):
with patch('sys.exit', new=MagicMock()) as sysmock:
with patch('sys.stderr', new=StringIO()) as mock_stderr:
sys.argv = ["cmd", "help"]
soyaml.removelistitem(["file", "key"])
sysmock.assert_called()
self.assertIn("Missing filename, key arg, or list item to remove", mock_stderr.getvalue())
def test_removelistitem(self):
filename = "/tmp/so-yaml_test-removelistitem.yaml"
file = open(filename, "w")
file.write("{key1: { child1: 123, child2: abc }, key2: false, key3: [a,b,c]}")
file.close()
soyaml.removelistitem([filename, "key3", "b"])
file = open(filename, "r")
actual = file.read()
file.close()
expected = "key1:\n child1: 123\n child2: abc\nkey2: false\nkey3:\n- a\n- c\n"
self.assertEqual(actual, expected)
def test_removelistitem_nested(self):
filename = "/tmp/so-yaml_test-removelistitem.yaml"
file = open(filename, "w")
file.write("{key1: { child1: 123, child2: [a,b,c] }, key2: false, key3: [e,f,g]}")
file.close()
soyaml.removelistitem([filename, "key1.child2", "b"])
file = open(filename, "r")
actual = file.read()
file.close()
expected = "key1:\n child1: 123\n child2:\n - a\n - c\nkey2: false\nkey3:\n- e\n- f\n- g\n"
self.assertEqual(actual, expected)
def test_removelistitem_nested_deep(self):
filename = "/tmp/so-yaml_test-removelistitem.yaml"
file = open(filename, "w")
file.write("{key1: { child1: 123, child2: { deep1: 45, deep2: [a,b,c] } }, key2: false, key3: [e,f,g]}")
file.close()
soyaml.removelistitem([filename, "key1.child2.deep2", "b"])
file = open(filename, "r")
actual = file.read()
file.close()
expected = "key1:\n child1: 123\n child2:\n deep1: 45\n deep2:\n - a\n - c\nkey2: false\nkey3:\n- e\n- f\n- g\n"
self.assertEqual(actual, expected)
def test_removelistitem_item_not_in_list(self):
filename = "/tmp/so-yaml_test-removelistitem.yaml"
file = open(filename, "w")
file.write("{key1: [a,b,c]}")
file.close()
soyaml.removelistitem([filename, "key1", "d"])
file = open(filename, "r")
actual = file.read()
file.close()
expected = "key1:\n- a\n- b\n- c\n"
self.assertEqual(actual, expected)
def test_removelistitem_key_noexist(self):
filename = "/tmp/so-yaml_test-removelistitem.yaml"
file = open(filename, "w")
file.write("{key1: { child1: 123, child2: { deep1: 45, deep2: [a,b,c] } }, key2: false, key3: [e,f,g]}")
file.close()
with patch('sys.exit', new=MagicMock()) as sysmock:
with patch('sys.stderr', new=StringIO()) as mock_stderr:
sys.argv = ["cmd", "removelistitem", filename, "key4", "h"]
soyaml.main()
sysmock.assert_called()
self.assertEqual("The key provided does not exist. No action was taken on the file.\n", mock_stderr.getvalue())
def test_removelistitem_key_noexist_deep(self):
filename = "/tmp/so-yaml_test-removelistitem.yaml"
file = open(filename, "w")
file.write("{key1: { child1: 123, child2: { deep1: 45, deep2: [a,b,c] } }, key2: false, key3: [e,f,g]}")
file.close()
with patch('sys.exit', new=MagicMock()) as sysmock:
with patch('sys.stderr', new=StringIO()) as mock_stderr:
sys.argv = ["cmd", "removelistitem", filename, "key1.child2.deep3", "h"]
soyaml.main()
sysmock.assert_called()
self.assertEqual("The key provided does not exist. No action was taken on the file.\n", mock_stderr.getvalue())
def test_removelistitem_key_nonlist(self):
filename = "/tmp/so-yaml_test-removelistitem.yaml"
file = open(filename, "w")
file.write("{key1: { child1: 123, child2: { deep1: 45, deep2: [a,b,c] } }, key2: false, key3: [e,f,g]}")
file.close()
with patch('sys.exit', new=MagicMock()) as sysmock:
with patch('sys.stderr', new=StringIO()) as mock_stderr:
sys.argv = ["cmd", "removelistitem", filename, "key1", "h"]
soyaml.main()
sysmock.assert_called()
self.assertEqual("The existing value for the given key is not a list. No action was taken on the file.\n", mock_stderr.getvalue())
def test_removelistitem_key_nonlist_deep(self):
filename = "/tmp/so-yaml_test-removelistitem.yaml"
file = open(filename, "w")
file.write("{key1: { child1: 123, child2: { deep1: 45, deep2: [a,b,c] } }, key2: false, key3: [e,f,g]}")
file.close()
with patch('sys.exit', new=MagicMock()) as sysmock:
with patch('sys.stderr', new=StringIO()) as mock_stderr:
sys.argv = ["cmd", "removelistitem", filename, "key1.child2.deep1", "h"]
soyaml.main()
sysmock.assert_called()
self.assertEqual("The existing value for the given key is not a list. No action was taken on the file.\n", mock_stderr.getvalue())

View File

@@ -274,7 +274,7 @@ check_os_updates() {
if [[ "$confirm" == [cC] ]]; then
echo "Continuing without updating packages"
elif [[ "$confirm" == [uU] ]]; then
echo "Applying Grid Updates"
echo "Applying Grid Updates. The following patch.os salt state may take a while depending on how many packages need to be updated."
update_flag=true
else
echo "Exiting soup"
@@ -426,6 +426,7 @@ preupgrade_changes() {
[[ "$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
[[ "$INSTALLEDVERSION" == 2.4.190 ]] && up_to_2.4.200
true
}
@@ -457,6 +458,7 @@ postupgrade_changes() {
[[ "$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
[[ "$POSTVERSION" == 2.4.190 ]] && post_to_2.4.200
true
}
@@ -636,6 +638,11 @@ post_to_2.4.190() {
POSTVERSION=2.4.190
}
post_to_2.4.200() {
echo "Nothing to apply"
POSTVERSION=2.4.200
}
repo_sync() {
echo "Sync the local repo."
su socore -c '/usr/sbin/so-repo-sync' || fail "Unable to complete so-repo-sync."
@@ -903,6 +910,12 @@ up_to_2.4.190() {
INSTALLEDVERSION=2.4.190
}
up_to_2.4.200() {
touch /opt/so/state/esfleet_logstash_config_pillar
INSTALLEDVERSION=2.4.200
}
add_hydra_pillars() {
mkdir -p /opt/so/saltstack/local/pillar/hydra
touch /opt/so/saltstack/local/pillar/hydra/soc_hydra.sls
@@ -1318,6 +1331,8 @@ upgrade_salt() {
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"
@@ -1679,7 +1694,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. Forward 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. Sensor 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

@@ -8,12 +8,9 @@
{% from 'vars/globals.map.jinja' import GLOBALS %}
{% from "pcap/config.map.jinja" import PCAPMERGED %}
{% from 'bpf/pcap.map.jinja' import PCAPBPF %}
{% set BPF_COMPILED = "" %}
{% from 'bpf/pcap.map.jinja' import PCAPBPF, PCAP_BPF_STATUS, PCAP_BPF_CALC, STENO_BPF_COMPILED %}
# PCAP Section
stenographergroup:
group.present:
- name: stenographer
@@ -40,18 +37,12 @@ pcap_sbin:
- group: 939
- file_mode: 755
{% 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:
{% if PCAPBPF and not PCAP_BPF_STATUS %}
stenoPCAPbpfcompilationfailure:
test.configurable_test_state:
- changes: False
- result: False
- comment: "BPF Compilation Failed - Discarding Specified BPF"
{% endif %}
- comment: "BPF Syntax Error - Discarding Specified BPF. Error: {{ PCAP_BPF_CALC['stderr'] }}"
{% endif %}
stenoconf:
@@ -64,7 +55,7 @@ stenoconf:
- template: jinja
- defaults:
PCAPMERGED: {{ PCAPMERGED }}
BPF_COMPILED: "{{ BPF_COMPILED }}"
STENO_BPF_COMPILED: "{{ STENO_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"{{ BPF_COMPILED }}]
, "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 }}]
, "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 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.
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.
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,6 +5,7 @@
{% 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:
@@ -57,6 +58,17 @@ 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

@@ -727,7 +727,8 @@ def check_hypervisor_disk_space(hypervisor: str, size_gb: int) -> Tuple[bool, Op
result = local.cmd(
hypervisor_minion,
'cmd.run',
["df -BG /nsm/libvirt/volumes | tail -1 | awk '{print $4}' | sed 's/G//'"]
["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:

View File

@@ -26,7 +26,7 @@
#======================================================================================================================
set -o nounset # Treat unset variables as an error
__ScriptVersion="2025.02.24"
__ScriptVersion="2025.09.03"
__ScriptName="bootstrap-salt.sh"
__ScriptFullName="$0"
@@ -48,6 +48,7 @@ __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)
#======================================================================================================================
@@ -171,12 +172,12 @@ __check_config_dir() {
case "$CC_DIR_NAME" in
http://*|https://*)
__fetch_url "/tmp/${CC_DIR_BASE}" "${CC_DIR_NAME}"
CC_DIR_NAME="/tmp/${CC_DIR_BASE}"
__fetch_url "${_TMP_DIR}/${CC_DIR_BASE}" "${CC_DIR_NAME}"
CC_DIR_NAME="${_TMP_DIR}/${CC_DIR_BASE}"
;;
ftp://*)
__fetch_url "/tmp/${CC_DIR_BASE}" "${CC_DIR_NAME}"
CC_DIR_NAME="/tmp/${CC_DIR_BASE}"
__fetch_url "${_TMP_DIR}/${CC_DIR_BASE}" "${CC_DIR_NAME}"
CC_DIR_NAME="${_TMP_DIR}/${CC_DIR_BASE}"
;;
*://*)
echoerror "Unsupported URI scheme for $CC_DIR_NAME"
@@ -194,22 +195,22 @@ __check_config_dir() {
case "$CC_DIR_NAME" in
*.tgz|*.tar.gz)
tar -zxf "${CC_DIR_NAME}" -C /tmp
tar -zxf "${CC_DIR_NAME}" -C ${_TMP_DIR}
CC_DIR_BASE=$(basename "${CC_DIR_BASE}" ".tgz")
CC_DIR_BASE=$(basename "${CC_DIR_BASE}" ".tar.gz")
CC_DIR_NAME="/tmp/${CC_DIR_BASE}"
CC_DIR_NAME="${_TMP_DIR}/${CC_DIR_BASE}"
;;
*.tbz|*.tar.bz2)
tar -xjf "${CC_DIR_NAME}" -C /tmp
tar -xjf "${CC_DIR_NAME}" -C ${_TMP_DIR}
CC_DIR_BASE=$(basename "${CC_DIR_BASE}" ".tbz")
CC_DIR_BASE=$(basename "${CC_DIR_BASE}" ".tar.bz2")
CC_DIR_NAME="/tmp/${CC_DIR_BASE}"
CC_DIR_NAME="${_TMP_DIR}/${CC_DIR_BASE}"
;;
*.txz|*.tar.xz)
tar -xJf "${CC_DIR_NAME}" -C /tmp
tar -xJf "${CC_DIR_NAME}" -C ${_TMP_DIR}
CC_DIR_BASE=$(basename "${CC_DIR_BASE}" ".txz")
CC_DIR_BASE=$(basename "${CC_DIR_BASE}" ".tar.xz")
CC_DIR_NAME="/tmp/${CC_DIR_BASE}"
CC_DIR_NAME="${_TMP_DIR}/${CC_DIR_BASE}"
;;
esac
@@ -245,6 +246,7 @@ __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"
@@ -281,7 +283,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/git/salt}
_SALT_GIT_CHECKOUT_DIR=${BS_SALT_GIT_CHECKOUT_DIR:-${_TMP_DIR}/git/salt}
_NO_DEPS=$BS_FALSE
_FORCE_SHALLOW_CLONE=$BS_FALSE
_DISABLE_SSL=$BS_FALSE
@@ -367,7 +369,7 @@ __usage() {
also be specified. Salt installation will be ommitted, but some of the
dependencies could be installed to write configuration with -j or -J.
-d Disables checking if Salt services are enabled to start on system boot.
You can also do this by touching /tmp/disable_salt_checks on the target
You can also do this by touching ${BS_TMP_DIR}/disable_salt_checks on the target
host. Default: \${BS_FALSE}
-D Show debug output
-f Force shallow cloning for git installations.
@@ -424,6 +426,9 @@ __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
@@ -436,7 +441,7 @@ __usage() {
EOT
} # ---------- end of function __usage ----------
while getopts ':hvnDc:g:Gx:k:s:MSWNXCPFUKIA:i:Lp:dH:bflV:J:j:rR:aqQ' opt
while getopts ':hvnDc:g:Gx:k:s:MSWNXCPFUKIA:i:Lp:dH:bflV:J:j:rR:T:aqQ' opt
do
case "${opt}" in
@@ -478,6 +483,7 @@ 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 ;;
@@ -495,10 +501,10 @@ done
shift $((OPTIND-1))
# Define our logging file and pipe paths
LOGFILE="/tmp/$( echo "$__ScriptName" | sed s/.sh/.log/g )"
LOGPIPE="/tmp/$( echo "$__ScriptName" | sed s/.sh/.logpipe/g )"
LOGFILE="${_TMP_DIR}/$( echo "$__ScriptName" | sed s/.sh/.log/g )"
LOGPIPE="${_TMP_DIR}/$( echo "$__ScriptName" | sed s/.sh/.logpipe/g )"
# Ensure no residual pipe exists
rm "$LOGPIPE" 2>/dev/null
rm -f "$LOGPIPE" 2>/dev/null
# Create our logging pipe
# On FreeBSD we have to use mkfifo instead of mknod
@@ -534,7 +540,7 @@ exec 2>"$LOGPIPE"
# 14 SIGALRM
# 15 SIGTERM
#----------------------------------------------------------------------------------------------------------------------
APT_ERR=$(mktemp /tmp/apt_error.XXXXXX)
APT_ERR=$(mktemp ${_TMP_DIR}/apt_error.XXXXXX)
__exit_cleanup() {
EXIT_CODE=$?
@@ -927,6 +933,11 @@ 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
@@ -1941,11 +1952,6 @@ __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=$?
@@ -1996,14 +2002,14 @@ __apt_get_upgrade_noinput() {
#----------------------------------------------------------------------------------------------------------------------
__temp_gpg_pub() {
if __check_command_exists mktemp; then
tempfile="$(mktemp /tmp/salt-gpg-XXXXXXXX.pub 2>/dev/null)"
tempfile="$(mktemp ${_TMP_DIR}/salt-gpg-XXXXXXXX.pub 2>/dev/null)"
if [ -z "$tempfile" ]; then
echoerror "Failed to create temporary file in /tmp"
echoerror "Failed to create temporary file in ${_TMP_DIR}"
return 1
fi
else
tempfile="/tmp/salt-gpg-$$.pub"
tempfile="${_TMP_DIR}/salt-gpg-$$.pub"
fi
echo $tempfile
@@ -2043,7 +2049,7 @@ __rpm_import_gpg() {
__fetch_url "$tempfile" "$url" || return 1
# At least on CentOS 8, a missing newline at the end causes:
# error: /tmp/salt-gpg-n1gKUb1u.pub: key 1 not an armored public key.
# error: ${_TMP_DIR}/salt-gpg-n1gKUb1u.pub: key 1 not an armored public key.
# shellcheck disable=SC1003,SC2086
sed -i -e '$a\' $tempfile
@@ -2109,7 +2115,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/git}"
__SALT_GIT_CHECKOUT_PARENT_DIR="${__SALT_GIT_CHECKOUT_PARENT_DIR:-${_TMP_DIR}/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}"
@@ -2162,7 +2168,7 @@ __git_clone_and_checkout() {
if [ "$__SHALLOW_CLONE" -eq $BS_TRUE ]; then
# Let's try 'treeless' cloning to speed up. Treeless cloning omits trees and blobs ('files')
# but includes metadata (commit history, tags, branches etc.
# but includes metadata (commit history, tags, branches etc.
# Test for "--filter" option introduced in git 2.19, the minimal version of git where the treeless
# cloning we need actually works
if [ "$(git clone 2>&1 | grep 'filter')" != "" ]; then
@@ -2390,14 +2396,14 @@ __overwriteconfig() {
# Make a tempfile to dump any python errors into.
if __check_command_exists mktemp; then
tempfile="$(mktemp /tmp/salt-config-XXXXXXXX 2>/dev/null)"
tempfile="$(mktemp ${_TMP_DIR}/salt-config-XXXXXXXX 2>/dev/null)"
if [ -z "$tempfile" ]; then
echoerror "Failed to create temporary file in /tmp"
echoerror "Failed to create temporary file in ${_TMP_DIR}"
return 1
fi
else
tempfile="/tmp/salt-config-$$"
tempfile="${_TMP_DIR}/salt-config-$$"
fi
if [ -n "$_PY_EXE" ]; then
@@ -2760,8 +2766,8 @@ __install_salt_from_repo() {
echoinfo "Installing salt using ${_py_exe}, $(${_py_exe} --version)"
cd "${_SALT_GIT_CHECKOUT_DIR}" || return 1
mkdir -p /tmp/git/deps
echodebug "Created directory /tmp/git/deps"
mkdir -p ${_TMP_DIR}/git/deps
echodebug "Created directory ${_TMP_DIR}/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"
@@ -2774,7 +2780,7 @@ __install_salt_from_repo() {
fi
fi
rm -f /tmp/git/deps/*
rm -f ${_TMP_DIR}/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"
@@ -2799,7 +2805,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/git/deps/ || return 1
mv dist/salt*.whl ${_TMP_DIR}/git/deps/ || return 1
cd "${__SALT_GIT_CHECKOUT_PARENT_DIR}" || return 1
@@ -2813,14 +2819,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/git/deps/salt*.whl'"
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} --global-option=--salt-config-dir=$_SALT_ETC_DIR --salt-cache-dir=${_SALT_CACHE_DIR} ${SETUP_PY_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"
${_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 || return 1
${_TMP_DIR}/git/deps/salt*.whl || return 1
echoinfo "Checking if Salt can be imported using ${_py_exe}"
CHECK_SALT_SCRIPT=$(cat << EOM
@@ -6295,8 +6301,8 @@ __get_packagesite_onedir_latest() {
}
__install_saltstack_photon_onedir_repository() {
echodebug "__install_saltstack_photon_onedir_repository() entry"
__install_saltstack_vmware_photon_os_onedir_repository() {
echodebug "__install_saltstack_vmware_photon_os_onedir_repository() entry"
if [ -n "$_PY_EXE" ] && [ "$_PY_MAJOR_VERSION" -ne 3 ]; then
echoerror "Python version is no longer supported, only Python 3"
@@ -6376,8 +6382,8 @@ __install_saltstack_photon_onedir_repository() {
return 0
}
install_photon_deps() {
echodebug "install_photon_deps() entry"
install_vmware_photon_os_deps() {
echodebug "install_vmware_photon_os_deps() entry"
if [ -n "$_PY_EXE" ] && [ "$_PY_MAJOR_VERSION" -ne 3 ]; then
echoerror "Python version is no longer supported, only Python 3"
@@ -6406,8 +6412,8 @@ install_photon_deps() {
return 0
}
install_photon_stable_post() {
echodebug "install_photon_stable_post() entry"
install_vmware_photon_os_stable_post() {
echodebug "install_vmware_photon_os_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
@@ -6424,8 +6430,8 @@ install_photon_stable_post() {
done
}
install_photon_git_deps() {
echodebug "install_photon_git_deps() entry"
install_vmware_photon_os_git_deps() {
echodebug "install_vmware_photon_os_git_deps() entry"
if [ -n "$_PY_EXE" ] && [ "$_PY_MAJOR_VERSION" -ne 3 ]; then
echoerror "Python version is no longer supported, only Python 3"
@@ -6463,7 +6469,7 @@ install_photon_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_photon_git_deps() distro major version, ${DISTRO_MAJOR_VERSION}"
echodebug "install_vmware_photon_os_git_deps() distro major version, ${DISTRO_MAJOR_VERSION}"
## Photon 5 container is missing systemd on default installation
if [ "${DISTRO_MAJOR_VERSION}" -lt 5 ]; then
@@ -6489,8 +6495,8 @@ install_photon_git_deps() {
return 0
}
install_photon_git() {
echodebug "install_photon_git() entry"
install_vmware_photon_os_git() {
echodebug "install_vmware_photon_os_git() entry"
if [ "${_PY_EXE}" != "" ]; then
_PYEXE=${_PY_EXE}
@@ -6500,7 +6506,7 @@ install_photon_git() {
return 1
fi
install_photon_git_deps
install_vmware_photon_os_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
@@ -6510,8 +6516,8 @@ install_photon_git() {
return 0
}
install_photon_git_post() {
echodebug "install_photon_git_post() entry"
install_vmware_photon_os_git_post() {
echodebug "install_vmware_photon_os_git_post() entry"
for fname in api master minion syndic; do
# Skip if not meant to be installed
@@ -6543,9 +6549,9 @@ install_photon_git_post() {
done
}
install_photon_restart_daemons() {
install_vmware_photon_os_restart_daemons() {
[ "$_START_DAEMONS" -eq $BS_FALSE ] && return
echodebug "install_photon_restart_daemons() entry"
echodebug "install_vmware_photon_os_restart_daemons() entry"
for fname in api master minion syndic; do
@@ -6567,8 +6573,8 @@ install_photon_restart_daemons() {
done
}
install_photon_check_services() {
echodebug "install_photon_check_services() entry"
install_vmware_photon_os_check_services() {
echodebug "install_vmware_photon_os_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
@@ -6585,8 +6591,8 @@ install_photon_check_services() {
return 0
}
install_photon_onedir_deps() {
echodebug "install_photon_onedir_deps() entry"
install_vmware_photon_os_onedir_deps() {
echodebug "install_vmware_photon_os_onedir_deps() entry"
if [ "$_UPGRADE_SYS" -eq $BS_TRUE ]; then
@@ -6600,17 +6606,17 @@ install_photon_onedir_deps() {
fi
if [ "$_DISABLE_REPOS" -eq "$BS_FALSE" ]; then
__install_saltstack_photon_onedir_repository || return 1
__install_saltstack_vmware_photon_os_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_photon_onedir_repository || return 1
__install_saltstack_vmware_photon_os_onedir_repository || return 1
fi
__PACKAGES="procps-ng sudo shadow"
__PACKAGES="procps-ng sudo shadow wget"
# shellcheck disable=SC2086
__tdnf_install_noinput ${__PACKAGES} || return 1
@@ -6626,9 +6632,9 @@ install_photon_onedir_deps() {
}
install_photon_onedir() {
install_vmware_photon_os_onedir() {
echodebug "install_photon_onedir() entry"
echodebug "install_vmware_photon_os_onedir() entry"
STABLE_REV=$ONEDIR_REV
_GENERIC_PKG_VERSION=""
@@ -6672,9 +6678,9 @@ install_photon_onedir() {
return 0
}
install_photon_onedir_post() {
install_vmware_photon_os_onedir_post() {
STABLE_REV=$ONEDIR_REV
install_photon_stable_post || return 1
install_vmware_photon_os_stable_post || return 1
return 0
}
@@ -7797,7 +7803,7 @@ install_macosx_git_deps() {
export PATH=/usr/local/bin:$PATH
fi
__fetch_url "/tmp/get-pip.py" "https://bootstrap.pypa.io/get-pip.py" || return 1
__fetch_url "${_TMP_DIR}/get-pip.py" "https://bootstrap.pypa.io/get-pip.py" || return 1
if [ -n "$_PY_EXE" ]; then
_PYEXE="${_PY_EXE}"
@@ -7807,7 +7813,7 @@ install_macosx_git_deps() {
fi
# Install PIP
$_PYEXE /tmp/get-pip.py || return 1
$_PYEXE ${_TMP_DIR}/get-pip.py || return 1
# shellcheck disable=SC2119
__git_clone_and_checkout || return 1
@@ -7819,9 +7825,9 @@ install_macosx_stable() {
install_macosx_stable_deps || return 1
__fetch_url "/tmp/${PKG}" "${SALTPKGCONFURL}" || return 1
__fetch_url "${_TMP_DIR}/${PKG}" "${SALTPKGCONFURL}" || return 1
/usr/sbin/installer -pkg "/tmp/${PKG}" -target / || return 1
/usr/sbin/installer -pkg "${_TMP_DIR}/${PKG}" -target / || return 1
return 0
}
@@ -7830,9 +7836,9 @@ install_macosx_onedir() {
install_macosx_onedir_deps || return 1
__fetch_url "/tmp/${PKG}" "${SALTPKGCONFURL}" || return 1
__fetch_url "${_TMP_DIR}/${PKG}" "${SALTPKGCONFURL}" || return 1
/usr/sbin/installer -pkg "/tmp/${PKG}" -target / || return 1
/usr/sbin/installer -pkg "${_TMP_DIR}/${PKG}" -target / || return 1
return 0
}

View File

@@ -15,7 +15,7 @@ sensoroni:
sensoronikey:
soc_host:
suripcap:
pcapMaxCount: 999999
pcapMaxCount: 100000
analyzers:
echotrail:
base_url: https://api.echotrail.io/insights/

View File

@@ -1364,6 +1364,8 @@ soc:
cases: soc
filedatastore:
jobDir: jobs
retryFailureIntervalMs: 600000
retryFailureMaxAttempts: 5
kratos:
hostUrl:
hydra:
@@ -1744,7 +1746,7 @@ soc:
showSubtitle: true
- name: DPD
description: Dynamic Protocol Detection errors
query: 'tags:dpd | groupby error.reason'
query: '(tags:dpd OR tags:analyzer) | groupby error.reason'
showSubtitle: true
- name: Files
description: Files grouped by mimetype
@@ -2010,7 +2012,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 | 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 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'
- 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'
@@ -2552,9 +2554,32 @@ soc:
assistant:
enabled: false
investigationPrompt: Investigate Alert ID {socId}
contextLimitSmall: 200000
contextLimitLarge: 1000000
compressContextPrompt: Summarize the conversation for context compaction
thresholdColorRatioLow: 0.5
thresholdColorRatioMed: 0.75
thresholdColorRatioMax: 1
lowBalanceColorAlert: 500000
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

@@ -43,10 +43,26 @@
No Virtual Machines Found
{%- endif %}
{%- else %}
{%- 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' %}
#### WARNING
Base domain has not been initialized.
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.
{%- endif %}
{%- endmacro -%}

View File

@@ -424,6 +424,17 @@ 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.
@@ -606,14 +617,9 @@ soc:
investigationPrompt:
description: Prompt given to Onion AI when beginning an investigation.
global: True
contextLimitSmall:
description: Smaller context limit for Onion AI.
compressContextPrompt:
description: Prompt given to Onion AI when summarizing a conversation in order to compress context.
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
@@ -630,6 +636,35 @@ soc:
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

@@ -14,7 +14,7 @@ include:
strelka_filestream:
docker_container.running:
- image: {{ GLOBALS.registry_host }}:5000/{{ GLOBALS.image_repo }}/so-strelka-filestream:{{ GLOBALS.so_version }}
- image: {{ GLOBALS.registry_host }}:5000/{{ GLOBALS.image_repo }}/so-strelka-manager:{{ 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-frontend:{{ GLOBALS.so_version }}
- image: {{ GLOBALS.registry_host }}:5000/{{ GLOBALS.image_repo }}/so-strelka-manager:{{ GLOBALS.so_version }}
- binds:
- /opt/so/conf/strelka/frontend/:/etc/strelka/:ro
- /nsm/strelka/log/:/var/log/strelka/:rw

View File

@@ -7,9 +7,47 @@
{% 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 %}
{% set BPF_STATUS = 0 %}
{% 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 %}
# Add Suricata Group
suricatagroup:
@@ -49,18 +87,11 @@ 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:
@@ -136,32 +167,6 @@ 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: 32768
block-size: 69632
block-timeout: 10
use-emergency-flush: "yes"
buffer-size: 32768
@@ -97,6 +97,11 @@ suricata:
- 4789
TEREDO_PORTS:
- 3544
SIP_PORTS:
- 5060
- 5061
GENEVE_PORTS:
- 6081
default-log-dir: /var/log/suricata/
stats:
enabled: "yes"
@@ -134,14 +139,6 @@ 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:
@@ -157,9 +154,6 @@ suricata:
totals: "yes"
threads: "no"
null-values: "yes"
syslog:
enabled: "no"
facility: local5
drop:
enabled: "no"
file-store:
@@ -206,6 +200,9 @@ suricata:
enabled: "yes"
detection-ports:
dp: 443
ja3-fingerprints: auto
ja4-fingerprints: auto
encryption-handling: track-only
dcerpc:
enabled: "yes"
ftp:
@@ -255,19 +252,21 @@ suricata:
libhtp:
default-config:
personality: IDS
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
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
response-body-decompress-layer-limit: 2
http-body-inline: auto
swf-decompression:
enabled: "yes"
enabled: "no"
type: both
compress-depth: 0
decompress-depth: 0
compress-depth: 100 KiB
decompress-depth: 100 KiB
randomize-inspection-sizes: "yes"
randomize-inspection-range: 10
double-decode-path: "no"
double-decode-query: "no"
server-config:
@@ -401,8 +400,12 @@ suricata:
vxlan:
enabled: true
ports: $VXLAN_PORTS
erspan:
geneve:
enabled: true
ports: $GENEVE_PORTS
max-layers: 16
recursion-level:
use-for-tracking: true
detect:
profile: medium
custom-values:
@@ -422,7 +425,12 @@ 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"

View File

@@ -10,6 +10,12 @@
{# before we change outputs back to list, enable pcap-log if suricata is the pcapengine #}
{% if GLOBALS.pcap_engine in ["SURICATA", "TRANSITION"] %}
{% from 'bpf/pcap.map.jinja' import PCAPBPF, PCAP_BPF_STATUS %}
{% if PCAPBPF and PCAP_BPF_STATUS %}
{% do SURICATAMERGED.config.outputs['pcap-log'].update({'bpf-filter': PCAPBPF|join(" ")}) %}
{% endif %}
{% do SURICATAMERGED.config.outputs['pcap-log'].update({'enabled': 'yes'}) %}
{# move the items in suricata.pcap into suricata.config.outputs.pcap-log. these items were placed under suricata.config for ease of access in SOC #}
{% do SURICATAMERGED.config.outputs['pcap-log'].update({'compression': SURICATAMERGED.pcap.compression}) %}

View File

@@ -190,6 +190,8 @@ suricata:
FTP_PORTS: *suriportgroup
VXLAN_PORTS: *suriportgroup
TEREDO_PORTS: *suriportgroup
SIP_PORTS: *suriportgroup
GENEVE_PORTS: *suriportgroup
outputs:
eve-log:
types:
@@ -209,7 +211,7 @@ suricata:
helpLink: suricata.html
pcap-log:
enabled:
description: This value is ignored by SO. pcapengine in globals takes precidence.
description: This value is ignored by SO. pcapengine in globals takes precedence.
readonly: True
helpLink: suricata.html
advanced: True
@@ -297,3 +299,10 @@ suricata:
ports:
description: Ports to listen for. This should be a variable.
helpLink: suricata.html
geneve:
enabled:
description: Enable VXLAN capabilities.
helpLink: suricata.html
ports:
description: Ports to listen for. This should be a variable.
helpLink: suricata.html

View File

@@ -29,7 +29,7 @@ suricata:
#custom: [Accept-Encoding, Accept-Language, Authorization]
# dump-all-headers: none
- dns:
version: 2
version: 3
enabled: "yes"
#requests: "no"
#responses: "no"

View File

@@ -7,5 +7,5 @@
. /usr/sbin/so-common
retry 60 3 'docker exec so-suricata /opt/suricata/bin/suricatasc -c reload-rules /var/run/suricata/suricata-command.socket' '{"message": "done", "return": "OK"}' || fail "The Suricata container was not ready in time."
retry 60 3 'docker exec so-suricata /opt/suricata/bin/suricatasc -c ruleset-reload-nonblocking /var/run/suricata/suricata-command.socket' '{"message": "done", "return": "OK"}' || fail "The Suricata container was not ready in time."
retry 60 3 'docker exec so-suricata /opt/suricata/bin/suricatasc -c reload-rules /var/run/suricata/suricata-command.socket' '{"message":"done","return":"OK"}' || fail "The Suricata container was not ready in time."
retry 60 3 'docker exec so-suricata /opt/suricata/bin/suricatasc -c ruleset-reload-nonblocking /var/run/suricata/suricata-command.socket' '{"message":"done","return":"OK"}' || fail "The Suricata container was not ready in time."

View File

@@ -8,8 +8,7 @@
{% from 'vars/globals.map.jinja' import GLOBALS %}
{% from "zeek/config.map.jinja" import ZEEKMERGED %}
{% from 'bpf/zeek.map.jinja' import ZEEKBPF %}
{% set BPF_STATUS = 0 %}
{% from 'bpf/zeek.map.jinja' import ZEEKBPF, ZEEK_BPF_STATUS, ZEEK_BPF_CALC %}
# Add Zeek group
zeekgroup:
@@ -158,18 +157,13 @@ zeekja4cfg:
- user: 937
- group: 939
# BPF compilation and configuration
{% if ZEEKBPF %}
{% set BPF_CALC = salt['cmd.script']('salt://common/tools/sbin/so-bpf-compile', GLOBALS.sensor.interface + ' ' + ZEEKBPF|join(" "),cwd='/root') %}
{% if BPF_CALC['stderr'] == "" %}
{% set BPF_STATUS = 1 %}
{% else %}
# BPF compilation failed
{% if ZEEKBPF and not ZEEK_BPF_STATUS %}
zeekbpfcompilationfailure:
test.configurable_test_state:
- changes: False
- result: False
- comment: "BPF Syntax Error - Discarding Specified BPF"
{% endif %}
- comment: "BPF Syntax Error - Discarding Specified BPF. Error: {{ ZEEK_BPF_CALC['stderr'] }}"
{% endif %}
zeekbpf:
@@ -177,7 +171,7 @@ zeekbpf:
- name: /opt/so/conf/zeek/bpf
- user: 940
- group: 940
{% if BPF_STATUS %}
{% if ZEEK_BPF_STATUS %}
- contents: {{ ZEEKBPF }}
{% else %}
- contents:

View File

@@ -45,7 +45,7 @@ zeek:
- protocols/ssh/geo-data
- protocols/ssh/detect-bruteforcing
- protocols/ssh/interesting-hostnames
- protocols/http/detect-sqli
- protocols/http/detect-sql-injection
- frameworks/files/hash-all-files
- frameworks/files/detect-MHR
- policy/frameworks/notice/extend-email/hostnames

View File

@@ -11,6 +11,8 @@ export {
option JA4S_enabled: bool = F;
option JA4S_raw: bool = F;
option JA4D_enabled: bool = F;
option JA4H_enabled: bool = F;
option JA4H_raw: bool = F;

View File

@@ -502,6 +502,7 @@ configure_minion() {
minion_type=desktop
fi
info "Configuring minion type as $minion_type"
logCmd "mkdir -p /etc/salt/minion.d"
echo "role: so-$minion_type" > /etc/salt/grains
local minion_config=/etc/salt/minion
@@ -541,20 +542,6 @@ configure_minion() {
"log_file: /opt/so/log/salt/minion"\
"#startup_states: highstate" >> "$minion_config"
# At the time the so-managerhype node does not yet have the bridge configured.
# The so-hypervisor node doesn't either, but it doesn't cause issues here.
local usebr0=false
if [ "$minion_type" == 'hypervisor' ]; then
usebr0=true
fi
local pillar_json="{\"host\": {\"mainint\": \"$MNIC\"}, \"usebr0\": $usebr0}"
info "Running: salt-call state.apply salt.mine_functions --local --file-root=../salt/ -l info pillar='$pillar_json'"
salt-call state.apply salt.mine_functions --local --file-root=../salt/ -l info pillar="$pillar_json"
{
logCmd "systemctl enable salt-minion";
logCmd "systemctl restart salt-minion";
} >> "$setup_log" 2>&1
}
checkin_at_boot() {
@@ -669,11 +656,11 @@ check_requirements() {
fi
if [[ $total_mem_hr -lt $req_mem ]]; then
whiptail_requirements_error "memory" "${total_mem_hr} GB" "${req_mem} GB"
if [[ $is_standalone || $is_heavynode ]]; then
echo "This install type will fail with less than $req_mem GB of memory. Exiting setup."
exit 0
fi
whiptail_requirements_error "memory" "${total_mem_hr} GB" "${req_mem} GB"
fi
if [[ $is_standalone || $is_heavynode ]]; then
if [[ $total_mem_hr -gt 15 && $total_mem_hr -lt 24 ]]; then
@@ -729,7 +716,7 @@ configure_network_sensor() {
fi
# Create the bond interface only if it doesn't already exist
nmcli -f name,uuid -p con | grep -q '$INTERFACE'
nmcli -f name,uuid -p con | grep -q "$INTERFACE"
local found_int=$?
if [[ $found_int != 0 ]]; then
@@ -798,25 +785,18 @@ configure_hyper_bridge() {
}
copy_salt_master_config() {
logCmd "mkdir /etc/salt"
title "Copy the Salt master config template to the proper directory"
if [ "$setup_type" = 'iso' ]; then
logCmd "cp /root/SecurityOnion/files/salt/master/master /etc/salt/master"
#logCmd "cp /root/SecurityOnion/files/salt/master/salt-master.service /usr/lib/systemd/system/salt-master.service"
else
logCmd "cp ../files/salt/master/master /etc/salt/master"
#logCmd "cp ../files/salt/master/salt-master.service /usr/lib/systemd/system/salt-master.service"
fi
info "Copying pillar and salt files in $temp_install_dir to $local_salt_dir"
logCmd "cp -R $temp_install_dir/pillar/ $local_salt_dir/"
if [ -d "$temp_install_dir"/salt ] ; then
logCmd "cp -R $temp_install_dir/salt/ $local_salt_dir/"
fi
# Restart the service so it picks up the changes
logCmd "systemctl daemon-reload"
logCmd "systemctl enable salt-master"
logCmd "systemctl restart salt-master"
}
create_local_nids_rules() {
@@ -1624,16 +1604,21 @@ proxy_validate() {
reserve_group_ids() {
# This is a hack to fix OS from taking group IDs that we need
logCmd "groupadd -g 920 docker"
logCmd "groupadd -g 928 kratos"
logCmd "groupadd -g 930 elasticsearch"
logCmd "groupadd -g 931 logstash"
logCmd "groupadd -g 932 kibana"
logCmd "groupadd -g 933 elastalert"
logCmd "groupadd -g 937 zeek"
logCmd "groupadd -g 938 salt"
logCmd "groupadd -g 939 socore"
logCmd "groupadd -g 940 suricata"
logCmd "groupadd -g 948 elastic-agent-pr"
logCmd "groupadd -g 949 elastic-agent"
logCmd "groupadd -g 941 stenographer"
logCmd "groupadd -g 945 ossec"
logCmd "groupadd -g 946 cyberchef"
logCmd "groupadd -g 947 elastic-fleet"
logCmd "groupadd -g 960 kafka"
}
reserve_ports() {
@@ -1646,6 +1631,12 @@ reserve_ports() {
fi
}
clear_previous_setup_results() {
# Disregard previous setup outcomes.
rm -f /root/failure
rm -f /root/success
}
reinstall_init() {
info "Putting system in state to run setup again"
@@ -1657,10 +1648,6 @@ reinstall_init() {
local service_retry_count=20
# Disregard previous install outcomes
rm -f /root/failure
rm -f /root/success
{
# remove all of root's cronjobs
logCmd "crontab -r -u root"
@@ -1939,11 +1926,12 @@ repo_sync_local() {
}
saltify() {
info "Installing Salt"
SALTVERSION=$(grep "version:" ../salt/salt/master.defaults.yaml | grep -o "[0-9]\+\.[0-9]\+")
info "Installing Salt $SALTVERSION"
chmod u+x ../salt/salt/scripts/bootstrap-salt.sh
if [[ $is_deb ]]; then
DEBIAN_FRONTEND=noninteractive retry 150 20 "apt-get -y -o Dpkg::Options::=\"--force-confdef\" -o Dpkg::Options::=\"--force-confold\" upgrade" >> "$setup_log" 2>&1 || fail_setup
DEBIAN_FRONTEND=noninteractive retry 30 10 "apt-get -y -o Dpkg::Options::=\"--force-confdef\" -o Dpkg::Options::=\"--force-confold\" upgrade" >> "$setup_log" 2>&1 || fail_setup
if [ $OSVER == "focal" ]; then update-alternatives --install /usr/bin/python python /usr/bin/python3.10 10; fi
local pkg_arr=(
'apache2-utils'
@@ -1956,16 +1944,11 @@ saltify() {
'jq'
'gnupg'
)
retry 150 20 "apt-get -y install ${pkg_arr[*]}" || fail_setup
retry 30 10 "apt-get -y install ${pkg_arr[*]}" || fail_setup
logCmd "mkdir -vp /etc/apt/keyrings"
logCmd "wget -q --inet4-only -O /etc/apt/keyrings/docker.pub https://download.docker.com/linux/ubuntu/gpg"
# Download public key
logCmd "curl -fsSL -o /etc/apt/keyrings/salt-archive-keyring-2023.pgp https://packages.broadcom.com/artifactory/api/security/keypair/SaltProjectKey/public"
# Create apt repo target configuration
echo "deb [signed-by=/etc/apt/keyrings/salt-archive-keyring-2023.pgp arch=amd64] https://packages.broadcom.com/artifactory/saltproject-deb/ stable main" | sudo tee /etc/apt/sources.list.d/salt.list
if [[ $is_ubuntu ]]; then
# Add Docker Repo
add-apt-repository -y "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
@@ -1976,45 +1959,50 @@ saltify() {
echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $OSVER stable" > /etc/apt/sources.list.d/docker.list
fi
logCmd "apt-key add /etc/apt/keyrings/salt-archive-keyring-2023.pgp"
#logCmd "apt-key add /opt/so/gpg/SALTSTACK-GPG-KEY.pub"
logCmd "apt-key add /etc/apt/keyrings/docker.pub"
# Add SO Saltstack Repo
#echo "deb https://repo.securityonion.net/file/securityonion-repo/ubuntu/20.04/amd64/salt3004.2/ focal main" > /etc/apt/sources.list.d/saltstack.list
# Ain't nothing but a GPG
retry 150 20 "apt-get update" "" "Err:" || fail_setup
retry 30 10 "apt-get update" "" "Err:" || fail_setup
if [[ $waitforstate ]]; then
retry 150 20 "apt-get -y install salt-common=$SALTVERSION salt-minion=$SALTVERSION salt-master=$SALTVERSION" || fail_setup
retry 150 20 "apt-mark hold salt-minion salt-common salt-master" || fail_setup
retry 150 20 "apt-get -y install python3-pip python3-dateutil python3-m2crypto python3-packaging python3-influxdb python3-lxml" || exit 1
retry 30 10 "bash ../salt/salt/scripts/bootstrap-salt.sh -M -X stable $SALTVERSION" || fail_setup
retry 30 10 "apt-mark hold salt-minion salt-common salt-master" || fail_setup
retry 30 10 "apt-get -y install python3-pip python3-dateutil python3-m2crypto python3-packaging python3-influxdb python3-lxml" || exit 1
else
retry 150 20 "apt-get -y install salt-common=$SALTVERSION salt-minion=$SALTVERSION" || fail_setup
retry 150 20 "apt-mark hold salt-minion salt-common" || fail_setup
retry 30 10 "bash ../salt/salt/scripts/bootstrap-salt.sh -X stable $SALTVERSION" || fail_setup
retry 30 10 "apt-mark hold salt-minion salt-common" || fail_setup
fi
fi
if [[ $is_rpm ]]; then
if [[ $waitforstate ]]; then
# install all for a manager
logCmd "dnf -y install salt-$SALTVERSION salt-master-$SALTVERSION salt-minion-$SALTVERSION"
retry 30 10 "bash ../salt/salt/scripts/bootstrap-salt.sh -r -M -X stable $SALTVERSION" || fail_setup
else
# We just need the minion
if [[ $is_airgap ]]; then
logCmd "dnf -y install salt salt-minion"
else
logCmd "dnf -y install salt-$SALTVERSION salt-minion-$SALTVERSION"
fi
# just a minion
retry 30 10 "bash ../salt/salt/scripts/bootstrap-salt.sh -r -X stable $SALTVERSION" || fail_setup
fi
fi
logCmd "mkdir -p /etc/salt/minion.d"
salt_install_module_deps
salt_patch_x509_v2
# At the time the so-managerhype node does not yet have the bridge configured.
# The so-hypervisor node doesn't either, but it doesn't cause issues here.
local usebr0=false
if [ "$minion_type" == 'hypervisor' ]; then
usebr0=true
fi
local pillar_json="{\"host\": {\"mainint\": \"$MNIC\"}, \"usebr0\": $usebr0}"
info "Running: salt-call state.apply salt.mine_functions --local --file-root=../salt/ -l info pillar='$pillar_json'"
salt-call state.apply salt.mine_functions --local --file-root=../salt/ -l info pillar="$pillar_json"
if [[ $waitforstate ]]; then
logCmd "systemctl enable salt-master";
logCmd "systemctl start salt-master";
fi
logCmd "systemctl enable salt-minion";
logCmd "systemctl restart salt-minion";
}
salt_install_module_deps() {

View File

@@ -132,6 +132,10 @@ if [[ -f /root/accept_changes ]]; then
reset_proxy
fi
# Previous setup attempts, even if setup doesn't actually start the installation,
# can leave behind results that may interfere with the current setup attempt.
clear_previous_setup_results
title "Parsing Username for Install"
parse_install_username
@@ -678,6 +682,8 @@ if ! [[ -f $install_opt_file ]]; then
fi
info "Reserving ports"
reserve_ports
info "Reserving group ids"
reserve_group_ids
info "Setting Paths"
# Set the paths
set_path
@@ -741,13 +747,12 @@ if ! [[ -f $install_opt_file ]]; then
securityonion_repo
# Update existing packages
update_packages
# Install salt
saltify
# Start the master service
# Put salt-master config in place
copy_salt_master_config
configure_minion "$minion_type"
# Install salt
saltify
check_sos_appliance
logCmd "salt-key -yd $MINION_ID"
sleep 2 # Debug RSA Key format errors
logCmd "salt-call state.show_top"
@@ -837,7 +842,10 @@ if ! [[ -f $install_opt_file ]]; then
if [[ $monints ]]; then
configure_network_sensor
fi
info "Reserving ports"
reserve_ports
info "Reserving group ids"
reserve_group_ids
# Set the version
mark_version
# Disable the setup from prompting at login
@@ -848,8 +856,8 @@ if ! [[ -f $install_opt_file ]]; then
gpg_rpm_import
securityonion_repo
update_packages
saltify
configure_minion "$minion_type"
saltify
check_sos_appliance
drop_install_options
hypervisor_local_states

View File

@@ -676,8 +676,8 @@ whiptail_install_type_dist_existing() {
EOM
install_type=$(whiptail --title "$whiptail_title" --menu "$node_msg" 19 75 7 \
"SENSOR" "Create a forward only sensor " \
"SEARCHNODE" "Add a search node with parsing " \
"SENSOR" "Add a Sensor Node for monitoring network traffic " \
"SEARCHNODE" "Add a Search Node with parsing " \
"FLEET" "Dedicated Elastic Fleet Node " \
"HEAVYNODE" "Sensor + Search Node " \
"IDH" "Intrusion Detection Honeypot Node " \