mirror of
https://github.com/Security-Onion-Solutions/securityonion.git
synced 2025-12-19 15:33:06 +01:00
Compare commits
21 Commits
reyesj2/el
...
2.4/main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ff0c6828b | ||
|
|
ddd6935e50 | ||
|
|
5588a56b24 | ||
|
|
12aed6e280 | ||
|
|
b2a469e08c | ||
|
|
285b0e4af9 | ||
|
|
f9edfd6391 | ||
|
|
f6301bc3e5 | ||
|
|
6c5c176b7d | ||
|
|
c6d52b5eb1 | ||
|
|
7cac528389 | ||
|
|
6fe817ca4a | ||
|
|
cb9a6fac25 | ||
|
|
a945768251 | ||
|
|
c6646e3821 | ||
|
|
99dc72cece | ||
|
|
94694d394e | ||
|
|
03dd746601 | ||
|
|
eec3373ae7 | ||
|
|
db45ce07ed | ||
|
|
33ada95bbc |
@@ -1,17 +1,17 @@
|
|||||||
### 2.4.190-20251024 ISO image released on 2025/10/24
|
### 2.4.200-20251216 ISO image released on 2025/12/16
|
||||||
|
|
||||||
|
|
||||||
### Download and Verify
|
### Download and Verify
|
||||||
|
|
||||||
2.4.190-20251024 ISO image:
|
2.4.200-20251216 ISO image:
|
||||||
https://download.securityonion.net/file/securityonion/securityonion-2.4.190-20251024.iso
|
https://download.securityonion.net/file/securityonion/securityonion-2.4.200-20251216.iso
|
||||||
|
|
||||||
MD5: 25358481FB876226499C011FC0710358
|
MD5: 07B38499952D1F2FD7B5AF10096D0043
|
||||||
SHA1: 0B26173C0CE136F2CA40A15046D1DFB78BCA1165
|
SHA1: 7F3A26839CA3CAEC2D90BB73D229D55E04C7D370
|
||||||
SHA256: 4FD9F62EDA672408828B3C0C446FE5EA9FF3C4EE8488A7AB1101544A3C487872
|
SHA256: 8D3AC735873A2EA8527E16A6A08C34BD5018CBC0925AC4096E15A0C99F591D5F
|
||||||
|
|
||||||
Signature for ISO image:
|
Signature for ISO image:
|
||||||
https://github.com/Security-Onion-Solutions/securityonion/raw/2.4/main/sigs/securityonion-2.4.190-20251024.iso.sig
|
https://github.com/Security-Onion-Solutions/securityonion/raw/2.4/main/sigs/securityonion-2.4.200-20251216.iso.sig
|
||||||
|
|
||||||
Signing key:
|
Signing key:
|
||||||
https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/2.4/main/KEYS
|
https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/2.4/main/KEYS
|
||||||
@@ -25,22 +25,22 @@ wget https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/2.
|
|||||||
|
|
||||||
Download the signature file for the ISO:
|
Download the signature file for the ISO:
|
||||||
```
|
```
|
||||||
wget https://github.com/Security-Onion-Solutions/securityonion/raw/2.4/main/sigs/securityonion-2.4.190-20251024.iso.sig
|
wget https://github.com/Security-Onion-Solutions/securityonion/raw/2.4/main/sigs/securityonion-2.4.200-20251216.iso.sig
|
||||||
```
|
```
|
||||||
|
|
||||||
Download the ISO image:
|
Download the ISO image:
|
||||||
```
|
```
|
||||||
wget https://download.securityonion.net/file/securityonion/securityonion-2.4.190-20251024.iso
|
wget https://download.securityonion.net/file/securityonion/securityonion-2.4.200-20251216.iso
|
||||||
```
|
```
|
||||||
|
|
||||||
Verify the downloaded ISO image using the signature file:
|
Verify the downloaded ISO image using the signature file:
|
||||||
```
|
```
|
||||||
gpg --verify securityonion-2.4.190-20251024.iso.sig securityonion-2.4.190-20251024.iso
|
gpg --verify securityonion-2.4.200-20251216.iso.sig securityonion-2.4.200-20251216.iso
|
||||||
```
|
```
|
||||||
|
|
||||||
The output should show "Good signature" and the Primary key fingerprint should match what's shown below:
|
The output should show "Good signature" and the Primary key fingerprint should match what's shown below:
|
||||||
```
|
```
|
||||||
gpg: Signature made Thu 23 Oct 2025 07:21:46 AM EDT using RSA key ID FE507013
|
gpg: Signature made Mon 15 Dec 2025 05:24:11 PM EST using RSA key ID FE507013
|
||||||
gpg: Good signature from "Security Onion Solutions, LLC <info@securityonionsolutions.com>"
|
gpg: Good signature from "Security Onion Solutions, LLC <info@securityonionsolutions.com>"
|
||||||
gpg: WARNING: This key is not certified with a trusted signature!
|
gpg: WARNING: This key is not certified with a trusted signature!
|
||||||
gpg: There is no indication that the signature belongs to the owner.
|
gpg: There is no indication that the signature belongs to the owner.
|
||||||
|
|||||||
@@ -299,6 +299,19 @@ elasticsearch:
|
|||||||
hot:
|
hot:
|
||||||
actions: {}
|
actions: {}
|
||||||
min_age: 0ms
|
min_age: 0ms
|
||||||
|
sos-backup:
|
||||||
|
index_sorting: false
|
||||||
|
index_template:
|
||||||
|
composed_of: []
|
||||||
|
ignore_missing_component_templates: []
|
||||||
|
index_patterns:
|
||||||
|
- sos-backup-*
|
||||||
|
priority: 501
|
||||||
|
template:
|
||||||
|
settings:
|
||||||
|
index:
|
||||||
|
number_of_replicas: 0
|
||||||
|
number_of_shards: 1
|
||||||
so-assistant-chat:
|
so-assistant-chat:
|
||||||
index_sorting: false
|
index_sorting: false
|
||||||
index_template:
|
index_template:
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ function getinstallinfo() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
source <(echo $INSTALLVARS)
|
export $(echo "$INSTALLVARS" | xargs)
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
log "ERROR" "Failed to source install variables"
|
log "ERROR" "Failed to source install variables"
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@@ -1125,40 +1125,35 @@ mkdir -p /nsm/backup/detections-migration/2-4-200
|
|||||||
cp /usr/sbin/so-rule-update /nsm/backup/detections-migration/2-4-200
|
cp /usr/sbin/so-rule-update /nsm/backup/detections-migration/2-4-200
|
||||||
cp /opt/so/conf/idstools/etc/rulecat.conf /nsm/backup/detections-migration/2-4-200
|
cp /opt/so/conf/idstools/etc/rulecat.conf /nsm/backup/detections-migration/2-4-200
|
||||||
|
|
||||||
if [[ -f /opt/so/conf/soc/so-detections-backup.py ]]; then
|
# Backup so-detection index via reindex
|
||||||
python3 /opt/so/conf/soc/so-detections-backup.py
|
echo "Creating sos-backup index template..."
|
||||||
|
template_result=$(/sbin/so-elasticsearch-query '_index_template/sos-backup' -X PUT \
|
||||||
|
--retry 5 --retry-delay 15 --retry-all-errors \
|
||||||
|
-d '{"index_patterns":["sos-backup-*"],"priority":501,"template":{"settings":{"index":{"number_of_replicas":0,"number_of_shards":1}}}}')
|
||||||
|
|
||||||
# Verify backup by comparing counts
|
if [[ -z "$template_result" ]] || ! echo "$template_result" | jq -e '.acknowledged == true' > /dev/null 2>&1; then
|
||||||
echo "Verifying detection overrides backup..."
|
echo "Error: Failed to create sos-backup index template"
|
||||||
es_override_count=$(/sbin/so-elasticsearch-query 'so-detection/_count' \
|
echo "$template_result"
|
||||||
--retry 5 --retry-delay 10 --retry-all-errors \
|
exit 1
|
||||||
-d '{"query": {"bool": {"must": [{"exists": {"field": "so_detection.overrides"}}]}}}' | jq -r '.count') || {
|
fi
|
||||||
echo " Error: Failed to query Elasticsearch for override count"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if [[ ! "$es_override_count" =~ ^[0-9]+$ ]]; then
|
BACKUP_INDEX="sos-backup-detection-$(date +%Y%m%d-%H%M%S)"
|
||||||
echo " Error: Invalid override count from Elasticsearch: '$es_override_count'"
|
echo "Backing up so-detection index to $BACKUP_INDEX..."
|
||||||
exit 1
|
reindex_result=$(/sbin/so-elasticsearch-query '_reindex?wait_for_completion=true' \
|
||||||
fi
|
--retry 5 --retry-delay 15 --retry-all-errors \
|
||||||
|
-X POST -d "{\"source\": {\"index\": \"so-detection\"}, \"dest\": {\"index\": \"$BACKUP_INDEX\"}}")
|
||||||
|
|
||||||
backup_override_count=$(find /nsm/backup/detections/repo/*/overrides -type f 2>/dev/null | wc -l)
|
if [[ -z "$reindex_result" ]]; then
|
||||||
|
echo "Error: Backup of detections failed - no response from Elasticsearch"
|
||||||
echo " Elasticsearch overrides: $es_override_count"
|
exit 1
|
||||||
echo " Backed up overrides: $backup_override_count"
|
elif echo "$reindex_result" | jq -e '.created >= 0' > /dev/null 2>&1; then
|
||||||
|
echo "Backup complete: $(echo "$reindex_result" | jq -r '.created') documents copied"
|
||||||
if [[ "$es_override_count" -gt 0 ]]; then
|
elif echo "$reindex_result" | grep -q "index_not_found_exception"; then
|
||||||
if [[ "$backup_override_count" -gt 0 ]]; then
|
echo "so-detection index does not exist, skipping backup"
|
||||||
echo " Override backup verified successfully"
|
|
||||||
else
|
|
||||||
echo " Error: Elasticsearch has $es_override_count overrides but backup has 0 files"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo " No overrides to backup"
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
echo "SOC Detections backup script not found, skipping detection backup"
|
echo "Error: Backup of detections failed"
|
||||||
|
echo "$reindex_result"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1179,11 +1174,12 @@ hash_normalized_file() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sed -E \
|
# Ensure trailing newline for consistent hashing regardless of source file
|
||||||
|
{ sed -E \
|
||||||
-e 's/^[[:space:]]+//; s/[[:space:]]+$//' \
|
-e 's/^[[:space:]]+//; s/[[:space:]]+$//' \
|
||||||
-e '/^$/d' \
|
-e '/^$/d' \
|
||||||
-e 's|--url=http://[^:]+:7788|--url=http://MANAGER:7788|' \
|
-e 's|--url=http://[^:]+:7788|--url=http://MANAGER:7788|' \
|
||||||
"$file" | sha256sum | awk '{print $1}'
|
"$file"; echo; } | sed '/^$/d' | sha256sum | awk '{print $1}'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Known-default hashes for so-rule-update (ETOPEN ruleset)
|
# Known-default hashes for so-rule-update (ETOPEN ruleset)
|
||||||
@@ -1279,6 +1275,13 @@ custom_found=0
|
|||||||
check_config_file "$SO_RULE_UPDATE" "KNOWN_SO_RULE_UPDATE_HASHES" || custom_found=1
|
check_config_file "$SO_RULE_UPDATE" "KNOWN_SO_RULE_UPDATE_HASHES" || custom_found=1
|
||||||
check_config_file "$RULECAT_CONF" "KNOWN_RULECAT_CONF_HASHES" || custom_found=1
|
check_config_file "$RULECAT_CONF" "KNOWN_RULECAT_CONF_HASHES" || custom_found=1
|
||||||
|
|
||||||
|
# Check for ETPRO rules on airgap systems
|
||||||
|
if [[ $is_airgap -eq 0 ]] && grep -q 'ETPRO ' /nsm/rules/suricata/emerging-all.rules 2>/dev/null; then
|
||||||
|
echo "ETPRO rules detected on airgap system - custom configuration"
|
||||||
|
echo "ETPRO rules detected on Airgap in /nsm/rules/suricata/emerging-all.rules" >> /opt/so/conf/soc/fingerprints/suricataengine.syncBlock
|
||||||
|
custom_found=1
|
||||||
|
fi
|
||||||
|
|
||||||
# If no custom configs found, remove syncBlock
|
# If no custom configs found, remove syncBlock
|
||||||
if [[ $custom_found -eq 0 ]]; then
|
if [[ $custom_found -eq 0 ]]; then
|
||||||
echo "idstools migration completed successfully - removing Suricata engine syncBlock"
|
echo "idstools migration completed successfully - removing Suricata engine syncBlock"
|
||||||
@@ -1304,6 +1307,7 @@ fi
|
|||||||
echo "Removing idstools symlink and scripts..."
|
echo "Removing idstools symlink and scripts..."
|
||||||
rm -rf /usr/sbin/so-idstools*
|
rm -rf /usr/sbin/so-idstools*
|
||||||
sed -i '/^#\?so-idstools$/d' /opt/so/conf/so-status/so-status.conf
|
sed -i '/^#\?so-idstools$/d' /opt/so/conf/so-status/so-status.conf
|
||||||
|
crontab -l | grep -v 'so-rule-update' | crontab -
|
||||||
|
|
||||||
# Backup the salt master config & manager pillar before editing it
|
# Backup the salt master config & manager pillar before editing it
|
||||||
cp /opt/so/saltstack/local/pillar/minions/$MINIONID.sls /nsm/backup/detections-migration/2-4-200/
|
cp /opt/so/saltstack/local/pillar/minions/$MINIONID.sls /nsm/backup/detections-migration/2-4-200/
|
||||||
@@ -1869,7 +1873,7 @@ main() {
|
|||||||
if [[ $is_airgap -eq 0 ]]; then
|
if [[ $is_airgap -eq 0 ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "Cleaning repos on remote Security Onion nodes."
|
echo "Cleaning repos on remote Security Onion nodes."
|
||||||
salt -C 'not *_eval and not *_manager and not *_managersearch and not *_standalone and G@os:CentOS' cmd.run "yum clean all"
|
salt -C 'not *_eval and not *_manager* and not *_standalone and G@os:OEL' cmd.run "dnf clean all"
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -2653,19 +2653,15 @@ soc:
|
|||||||
thresholdColorRatioMax: 1
|
thresholdColorRatioMax: 1
|
||||||
availableModels:
|
availableModels:
|
||||||
- id: sonnet-4.5
|
- id: sonnet-4.5
|
||||||
displayName: Claude Sonnet 4.5
|
displayName: Claude Sonnet 4.5 ($$$)
|
||||||
|
origin: USA
|
||||||
contextLimitSmall: 200000
|
contextLimitSmall: 200000
|
||||||
contextLimitLarge: 1000000
|
contextLimitLarge: 1000000
|
||||||
lowBalanceColorAlert: 500000
|
lowBalanceColorAlert: 500000
|
||||||
enabled: true
|
enabled: true
|
||||||
- id: gptoss-120b
|
|
||||||
displayName: GPT-OSS 120B
|
|
||||||
contextLimitSmall: 128000
|
|
||||||
contextLimitLarge: 128000
|
|
||||||
lowBalanceColorAlert: 500000
|
|
||||||
enabled: true
|
|
||||||
- id: qwen-235b
|
- id: qwen-235b
|
||||||
displayName: QWEN 235B
|
displayName: QWEN 235B ($)
|
||||||
|
origin: China
|
||||||
contextLimitSmall: 256000
|
contextLimitSmall: 256000
|
||||||
contextLimitLarge: 256000
|
contextLimitLarge: 256000
|
||||||
lowBalanceColorAlert: 500000
|
lowBalanceColorAlert: 500000
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
# This script queries Elasticsearch for Custom Detections and all Overrides,
|
# This script queries Elasticsearch for Custom Detections and all Overrides,
|
||||||
# and git commits them to disk at $OUTPUT_DIR
|
# and git commits them to disk at $OUTPUT_DIR
|
||||||
|
|
||||||
|
import argparse
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import json
|
import json
|
||||||
@@ -18,10 +19,10 @@ from datetime import datetime
|
|||||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
ES_URL = "https://localhost:9200/so-detection/_search"
|
DEFAULT_INDEX = "so-detection"
|
||||||
|
DEFAULT_OUTPUT_DIR = "/nsm/backup/detections/repo"
|
||||||
QUERY_DETECTIONS = '{"query": {"bool": {"must": [{"match_all": {}}, {"term": {"so_detection.ruleset": "__custom__"}}]}},"size": 10000}'
|
QUERY_DETECTIONS = '{"query": {"bool": {"must": [{"match_all": {}}, {"term": {"so_detection.ruleset": "__custom__"}}]}},"size": 10000}'
|
||||||
QUERY_OVERRIDES = '{"query": {"bool": {"must": [{"exists": {"field": "so_detection.overrides"}}]}},"size": 10000}'
|
QUERY_OVERRIDES = '{"query": {"bool": {"must": [{"exists": {"field": "so_detection.overrides"}}]}},"size": 10000}'
|
||||||
OUTPUT_DIR = "/nsm/backup/detections/repo"
|
|
||||||
AUTH_FILE = "/opt/so/conf/elasticsearch/curl.config"
|
AUTH_FILE = "/opt/so/conf/elasticsearch/curl.config"
|
||||||
|
|
||||||
def get_auth_credentials(auth_file):
|
def get_auth_credentials(auth_file):
|
||||||
@@ -30,9 +31,10 @@ def get_auth_credentials(auth_file):
|
|||||||
if line.startswith('user ='):
|
if line.startswith('user ='):
|
||||||
return line.split('=', 1)[1].strip().replace('"', '')
|
return line.split('=', 1)[1].strip().replace('"', '')
|
||||||
|
|
||||||
def query_elasticsearch(query, auth):
|
def query_elasticsearch(query, auth, index):
|
||||||
|
url = f"https://localhost:9200/{index}/_search"
|
||||||
headers = {"Content-Type": "application/json"}
|
headers = {"Content-Type": "application/json"}
|
||||||
response = requests.get(ES_URL, headers=headers, data=query, auth=auth, verify=False)
|
response = requests.get(url, headers=headers, data=query, auth=auth, verify=False)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
@@ -47,12 +49,12 @@ def save_content(hit, base_folder, subfolder="", extension="txt"):
|
|||||||
f.write(content)
|
f.write(content)
|
||||||
return file_path
|
return file_path
|
||||||
|
|
||||||
def save_overrides(hit):
|
def save_overrides(hit, output_dir):
|
||||||
so_detection = hit["_source"]["so_detection"]
|
so_detection = hit["_source"]["so_detection"]
|
||||||
public_id = so_detection["publicId"]
|
public_id = so_detection["publicId"]
|
||||||
overrides = so_detection["overrides"]
|
overrides = so_detection["overrides"]
|
||||||
language = so_detection["language"]
|
language = so_detection["language"]
|
||||||
folder = os.path.join(OUTPUT_DIR, language, "overrides")
|
folder = os.path.join(output_dir, language, "overrides")
|
||||||
os.makedirs(folder, exist_ok=True)
|
os.makedirs(folder, exist_ok=True)
|
||||||
extension = "yaml" if language == "sigma" else "txt"
|
extension = "yaml" if language == "sigma" else "txt"
|
||||||
file_path = os.path.join(folder, f"{public_id}.{extension}")
|
file_path = os.path.join(folder, f"{public_id}.{extension}")
|
||||||
@@ -60,20 +62,20 @@ def save_overrides(hit):
|
|||||||
f.write('\n'.join(json.dumps(override) for override in overrides) if isinstance(overrides, list) else overrides)
|
f.write('\n'.join(json.dumps(override) for override in overrides) if isinstance(overrides, list) else overrides)
|
||||||
return file_path
|
return file_path
|
||||||
|
|
||||||
def ensure_git_repo():
|
def ensure_git_repo(output_dir):
|
||||||
if not os.path.isdir(os.path.join(OUTPUT_DIR, '.git')):
|
if not os.path.isdir(os.path.join(output_dir, '.git')):
|
||||||
subprocess.run(["git", "config", "--global", "init.defaultBranch", "main"], check=True)
|
subprocess.run(["git", "config", "--global", "init.defaultBranch", "main"], check=True)
|
||||||
subprocess.run(["git", "-C", OUTPUT_DIR, "init"], check=True)
|
subprocess.run(["git", "-C", output_dir, "init"], check=True)
|
||||||
subprocess.run(["git", "-C", OUTPUT_DIR, "remote", "add", "origin", "default"], check=True)
|
subprocess.run(["git", "-C", output_dir, "remote", "add", "origin", "default"], check=True)
|
||||||
|
|
||||||
def commit_changes():
|
def commit_changes(output_dir):
|
||||||
ensure_git_repo()
|
ensure_git_repo(output_dir)
|
||||||
subprocess.run(["git", "-C", OUTPUT_DIR, "config", "user.email", "securityonion@local.invalid"], check=True)
|
subprocess.run(["git", "-C", output_dir, "config", "user.email", "securityonion@local.invalid"], check=True)
|
||||||
subprocess.run(["git", "-C", OUTPUT_DIR, "config", "user.name", "securityonion"], check=True)
|
subprocess.run(["git", "-C", output_dir, "config", "user.name", "securityonion"], check=True)
|
||||||
subprocess.run(["git", "-C", OUTPUT_DIR, "add", "."], check=True)
|
subprocess.run(["git", "-C", output_dir, "add", "."], check=True)
|
||||||
status_result = subprocess.run(["git", "-C", OUTPUT_DIR, "status"], capture_output=True, text=True)
|
status_result = subprocess.run(["git", "-C", output_dir, "status"], capture_output=True, text=True)
|
||||||
print(status_result.stdout)
|
print(status_result.stdout)
|
||||||
commit_result = subprocess.run(["git", "-C", OUTPUT_DIR, "commit", "-m", "Update detections and overrides"], check=False, capture_output=True)
|
commit_result = subprocess.run(["git", "-C", output_dir, "commit", "-m", "Update detections and overrides"], check=False, capture_output=True)
|
||||||
if commit_result.returncode == 1:
|
if commit_result.returncode == 1:
|
||||||
print("No changes to commit.")
|
print("No changes to commit.")
|
||||||
elif commit_result.returncode == 0:
|
elif commit_result.returncode == 0:
|
||||||
@@ -81,28 +83,40 @@ def commit_changes():
|
|||||||
else:
|
else:
|
||||||
commit_result.check_returncode()
|
commit_result.check_returncode()
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
parser = argparse.ArgumentParser(description="Backup custom detections and overrides from Elasticsearch")
|
||||||
|
parser.add_argument("--output", "-o", default=DEFAULT_OUTPUT_DIR,
|
||||||
|
help=f"Output directory for backups (default: {DEFAULT_OUTPUT_DIR})")
|
||||||
|
parser.add_argument("--index", "-i", default=DEFAULT_INDEX,
|
||||||
|
help=f"Elasticsearch index to query (default: {DEFAULT_INDEX})")
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
args = parse_args()
|
||||||
|
output_dir = args.output
|
||||||
|
index = args.index
|
||||||
|
|
||||||
try:
|
try:
|
||||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
print(f"Backing up Custom Detections and all Overrides to {OUTPUT_DIR} - {timestamp}\n")
|
print(f"Backing up Custom Detections and all Overrides to {output_dir} - {timestamp}\n")
|
||||||
|
|
||||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
auth_credentials = get_auth_credentials(AUTH_FILE)
|
auth_credentials = get_auth_credentials(AUTH_FILE)
|
||||||
username, password = auth_credentials.split(':', 1)
|
username, password = auth_credentials.split(':', 1)
|
||||||
auth = HTTPBasicAuth(username, password)
|
auth = HTTPBasicAuth(username, password)
|
||||||
|
|
||||||
# Query and save custom detections
|
# Query and save custom detections
|
||||||
detections = query_elasticsearch(QUERY_DETECTIONS, auth)["hits"]["hits"]
|
detections = query_elasticsearch(QUERY_DETECTIONS, auth, index)["hits"]["hits"]
|
||||||
for hit in detections:
|
for hit in detections:
|
||||||
save_content(hit, OUTPUT_DIR, hit["_source"]["so_detection"]["language"], "yaml" if hit["_source"]["so_detection"]["language"] == "sigma" else "txt")
|
save_content(hit, output_dir, hit["_source"]["so_detection"]["language"], "yaml" if hit["_source"]["so_detection"]["language"] == "sigma" else "txt")
|
||||||
|
|
||||||
# Query and save overrides
|
# Query and save overrides
|
||||||
overrides = query_elasticsearch(QUERY_OVERRIDES, auth)["hits"]["hits"]
|
overrides = query_elasticsearch(QUERY_OVERRIDES, auth, index)["hits"]["hits"]
|
||||||
for hit in overrides:
|
for hit in overrides:
|
||||||
save_overrides(hit)
|
save_overrides(hit, output_dir)
|
||||||
|
|
||||||
commit_changes()
|
commit_changes(output_dir)
|
||||||
|
|
||||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
print(f"Backup Completed - {timestamp}")
|
print(f"Backup Completed - {timestamp}")
|
||||||
|
|||||||
@@ -58,11 +58,11 @@ class TestBackupScript(unittest.TestCase):
|
|||||||
mock_response.raise_for_status = MagicMock()
|
mock_response.raise_for_status = MagicMock()
|
||||||
mock_get.return_value = mock_response
|
mock_get.return_value = mock_response
|
||||||
|
|
||||||
response = ds.query_elasticsearch(ds.QUERY_DETECTIONS, self.auth)
|
response = ds.query_elasticsearch(ds.QUERY_DETECTIONS, self.auth, ds.DEFAULT_INDEX)
|
||||||
|
|
||||||
self.assertEqual(response, {'hits': {'hits': []}})
|
self.assertEqual(response, {'hits': {'hits': []}})
|
||||||
mock_get.assert_called_once_with(
|
mock_get.assert_called_once_with(
|
||||||
ds.ES_URL,
|
f"https://localhost:9200/{ds.DEFAULT_INDEX}/_search",
|
||||||
headers={"Content-Type": "application/json"},
|
headers={"Content-Type": "application/json"},
|
||||||
data=ds.QUERY_DETECTIONS,
|
data=ds.QUERY_DETECTIONS,
|
||||||
auth=self.auth,
|
auth=self.auth,
|
||||||
@@ -81,7 +81,7 @@ class TestBackupScript(unittest.TestCase):
|
|||||||
@patch('os.makedirs')
|
@patch('os.makedirs')
|
||||||
@patch('builtins.open', new_callable=mock_open)
|
@patch('builtins.open', new_callable=mock_open)
|
||||||
def test_save_overrides(self, mock_file, mock_makedirs):
|
def test_save_overrides(self, mock_file, mock_makedirs):
|
||||||
file_path = ds.save_overrides(self.mock_override_hit)
|
file_path = ds.save_overrides(self.mock_override_hit, self.output_dir)
|
||||||
expected_path = f'{self.output_dir}/sigma/overrides/test_id.yaml'
|
expected_path = f'{self.output_dir}/sigma/overrides/test_id.yaml'
|
||||||
self.assertEqual(file_path, expected_path)
|
self.assertEqual(file_path, expected_path)
|
||||||
mock_makedirs.assert_called_once_with(f'{self.output_dir}/sigma/overrides', exist_ok=True)
|
mock_makedirs.assert_called_once_with(f'{self.output_dir}/sigma/overrides', exist_ok=True)
|
||||||
@@ -91,7 +91,7 @@ class TestBackupScript(unittest.TestCase):
|
|||||||
def test_ensure_git_repo(self, mock_run):
|
def test_ensure_git_repo(self, mock_run):
|
||||||
mock_run.return_value = MagicMock(returncode=0)
|
mock_run.return_value = MagicMock(returncode=0)
|
||||||
|
|
||||||
ds.ensure_git_repo()
|
ds.ensure_git_repo(self.output_dir)
|
||||||
|
|
||||||
mock_run.assert_has_calls([
|
mock_run.assert_has_calls([
|
||||||
call(["git", "config", "--global", "init.defaultBranch", "main"], check=True),
|
call(["git", "config", "--global", "init.defaultBranch", "main"], check=True),
|
||||||
@@ -108,7 +108,7 @@ class TestBackupScript(unittest.TestCase):
|
|||||||
mock_run.side_effect = [mock_status_result, mock_commit_result, MagicMock(returncode=0), MagicMock(returncode=0), MagicMock(returncode=0), MagicMock(returncode=0), MagicMock(returncode=0), MagicMock(returncode=0)]
|
mock_run.side_effect = [mock_status_result, mock_commit_result, MagicMock(returncode=0), MagicMock(returncode=0), MagicMock(returncode=0), MagicMock(returncode=0), MagicMock(returncode=0), MagicMock(returncode=0)]
|
||||||
|
|
||||||
print("Running test_commit_changes...")
|
print("Running test_commit_changes...")
|
||||||
ds.commit_changes()
|
ds.commit_changes(self.output_dir)
|
||||||
print("Finished test_commit_changes.")
|
print("Finished test_commit_changes.")
|
||||||
|
|
||||||
mock_run.assert_has_calls([
|
mock_run.assert_has_calls([
|
||||||
@@ -120,13 +120,18 @@ class TestBackupScript(unittest.TestCase):
|
|||||||
])
|
])
|
||||||
|
|
||||||
@patch('builtins.print')
|
@patch('builtins.print')
|
||||||
@patch('so-detections-backup.commit_changes')
|
@patch.object(ds, 'commit_changes')
|
||||||
@patch('so-detections-backup.save_overrides')
|
@patch.object(ds, 'save_overrides')
|
||||||
@patch('so-detections-backup.save_content')
|
@patch.object(ds, 'save_content')
|
||||||
@patch('so-detections-backup.query_elasticsearch')
|
@patch.object(ds, 'query_elasticsearch')
|
||||||
@patch('so-detections-backup.get_auth_credentials')
|
@patch.object(ds, 'get_auth_credentials')
|
||||||
@patch('os.makedirs')
|
@patch('os.makedirs')
|
||||||
def test_main(self, mock_makedirs, mock_get_auth, mock_query, mock_save_content, mock_save_overrides, mock_commit, mock_print):
|
@patch.object(ds, 'parse_args')
|
||||||
|
def test_main(self, mock_parse_args, mock_makedirs, mock_get_auth, mock_query, mock_save_content, mock_save_overrides, mock_commit, mock_print):
|
||||||
|
mock_args = MagicMock()
|
||||||
|
mock_args.output = self.output_dir
|
||||||
|
mock_args.index = ds.DEFAULT_INDEX
|
||||||
|
mock_parse_args.return_value = mock_args
|
||||||
mock_get_auth.return_value = self.auth_credentials
|
mock_get_auth.return_value = self.auth_credentials
|
||||||
mock_query.side_effect = [
|
mock_query.side_effect = [
|
||||||
{'hits': {'hits': [{"_source": {"so_detection": {"publicId": "1", "content": "content1", "language": "sigma"}}}]}},
|
{'hits': {'hits': [{"_source": {"so_detection": {"publicId": "1", "content": "content1", "language": "sigma"}}}]}},
|
||||||
@@ -140,8 +145,8 @@ class TestBackupScript(unittest.TestCase):
|
|||||||
mock_makedirs.assert_called_once_with(self.output_dir, exist_ok=True)
|
mock_makedirs.assert_called_once_with(self.output_dir, exist_ok=True)
|
||||||
mock_get_auth.assert_called_once_with(ds.AUTH_FILE)
|
mock_get_auth.assert_called_once_with(ds.AUTH_FILE)
|
||||||
mock_query.assert_has_calls([
|
mock_query.assert_has_calls([
|
||||||
call(ds.QUERY_DETECTIONS, self.auth),
|
call(ds.QUERY_DETECTIONS, self.auth, ds.DEFAULT_INDEX),
|
||||||
call(ds.QUERY_OVERRIDES, self.auth)
|
call(ds.QUERY_OVERRIDES, self.auth, ds.DEFAULT_INDEX)
|
||||||
])
|
])
|
||||||
mock_save_content.assert_called_once_with(
|
mock_save_content.assert_called_once_with(
|
||||||
{"_source": {"so_detection": {"publicId": "1", "content": "content1", "language": "sigma"}}},
|
{"_source": {"so_detection": {"publicId": "1", "content": "content1", "language": "sigma"}}},
|
||||||
@@ -150,9 +155,10 @@ class TestBackupScript(unittest.TestCase):
|
|||||||
"yaml"
|
"yaml"
|
||||||
)
|
)
|
||||||
mock_save_overrides.assert_called_once_with(
|
mock_save_overrides.assert_called_once_with(
|
||||||
{"_source": {"so_detection": {"publicId": "2", "overrides": [{"key": "value"}], "language": "suricata"}}}
|
{"_source": {"so_detection": {"publicId": "2", "overrides": [{"key": "value"}], "language": "suricata"}}},
|
||||||
|
self.output_dir
|
||||||
)
|
)
|
||||||
mock_commit.assert_called_once()
|
mock_commit.assert_called_once_with(self.output_dir)
|
||||||
mock_print.assert_called()
|
mock_print.assert_called()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -708,6 +708,9 @@ soc:
|
|||||||
- field: displayName
|
- field: displayName
|
||||||
label: Display Name
|
label: Display Name
|
||||||
required: True
|
required: True
|
||||||
|
- field: origin
|
||||||
|
label: Country of Origin for the Model Training
|
||||||
|
required: false
|
||||||
- field: contextLimitSmall
|
- field: contextLimitSmall
|
||||||
label: Context Limit (Small)
|
label: Context Limit (Small)
|
||||||
forcedType: int
|
forcedType: int
|
||||||
|
|||||||
BIN
sigs/securityonion-2.4.200-20251216.iso.sig
Normal file
BIN
sigs/securityonion-2.4.200-20251216.iso.sig
Normal file
Binary file not shown.
Reference in New Issue
Block a user