MirrorNet Core (secubox-mirrornet): - DID-based identity (did:plc:) with keypair management - Peer reputation scoring (0-100) with trust levels - Service mirroring via reverse proxy chaining - Enhanced gossip protocol with priority routing - Health monitoring with anomaly detection - mirrorctl CLI with 30+ commands Identity Package (secubox-identity): - Standalone DID generation (AT Protocol compatible) - HMAC-SHA256 keys with Ed25519 fallback - Key rotation with backup support - Trust scoring integration - identityctl CLI with 25+ commands P2P Intel Package (secubox-p2p-intel): - Signed IOC sharing for mesh - Collectors: CrowdSec, mitmproxy, WAF, DNS Guard - Cryptographic signing and validation - Source trust verification - Application: nftables/iptables/CrowdSec - Approval workflow for manual review - p2p-intelctl CLI with 20+ commands LuCI Dashboard (luci-app-secubox-mirror): - Identity card with DID, hostname, role - Peer reputation table with trust levels - Gossip protocol statistics - Health alerts with acknowledgment - RPCD handler with 15 methods Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
247 lines
6.4 KiB
Bash
247 lines
6.4 KiB
Bash
#!/bin/sh
|
|
# P2P Intel Validator - Validate and score received IOCs
|
|
|
|
. /lib/functions.sh
|
|
|
|
INTEL_DIR="/var/lib/p2p-intel"
|
|
RECEIVED_DIR="$INTEL_DIR/received"
|
|
VALIDATED_DIR="$INTEL_DIR/validated"
|
|
REJECTED_DIR="$INTEL_DIR/rejected"
|
|
|
|
# Initialize validator
|
|
validator_init() {
|
|
mkdir -p "$RECEIVED_DIR" "$VALIDATED_DIR" "$REJECTED_DIR"
|
|
}
|
|
|
|
# Check if IOC is too old
|
|
_is_too_old() {
|
|
local timestamp="$1"
|
|
local max_age_hours
|
|
max_age_hours=$(uci -q get p2p-intel.validation.max_age_hours || echo "168")
|
|
|
|
local max_age_seconds=$((max_age_hours * 3600))
|
|
local current_time
|
|
current_time=$(date +%s)
|
|
local age=$((current_time - timestamp))
|
|
|
|
[ "$age" -gt "$max_age_seconds" ]
|
|
}
|
|
|
|
# Check if source is trusted
|
|
_is_source_trusted() {
|
|
local source_did="$1"
|
|
local min_trust
|
|
min_trust=$(uci -q get p2p-intel.validation.min_source_trust || echo "40")
|
|
|
|
if [ -f /usr/lib/secubox-identity/trust.sh ]; then
|
|
. /usr/lib/secubox-identity/trust.sh
|
|
local score
|
|
score=$(trust_get_score "$source_did")
|
|
[ "$score" -ge "$min_trust" ]
|
|
else
|
|
# No trust system, accept all
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
# Check if IP is in local subnet (whitelist)
|
|
_is_local_ip() {
|
|
local ip="$1"
|
|
|
|
# Check common local ranges
|
|
case "$ip" in
|
|
10.*|172.1[6-9].*|172.2[0-9].*|172.3[0-1].*|192.168.*|127.*)
|
|
return 0
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Validate single IOC
|
|
validate_ioc() {
|
|
local ioc="$1"
|
|
local source_did="$2"
|
|
|
|
local ioc_type value timestamp
|
|
ioc_type=$(echo "$ioc" | jsonfilter -e '@.type' 2>/dev/null)
|
|
value=$(echo "$ioc" | jsonfilter -e '@.value' 2>/dev/null)
|
|
timestamp=$(echo "$ioc" | jsonfilter -e '@.collected_at' 2>/dev/null)
|
|
|
|
# Check required fields
|
|
if [ -z "$ioc_type" ] || [ -z "$value" ]; then
|
|
echo '{"valid": false, "reason": "missing_fields"}'
|
|
return 1
|
|
fi
|
|
|
|
# Check age
|
|
if [ -n "$timestamp" ] && _is_too_old "$timestamp"; then
|
|
echo '{"valid": false, "reason": "too_old"}'
|
|
return 1
|
|
fi
|
|
|
|
# Check source trust
|
|
if [ -n "$source_did" ]; then
|
|
if ! _is_source_trusted "$source_did"; then
|
|
echo '{"valid": false, "reason": "untrusted_source"}'
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# Type-specific validation
|
|
case "$ioc_type" in
|
|
ip)
|
|
# Validate IP format
|
|
if ! echo "$value" | grep -qE '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'; then
|
|
echo '{"valid": false, "reason": "invalid_ip_format"}'
|
|
return 1
|
|
fi
|
|
|
|
# Check whitelist
|
|
local whitelist_local
|
|
whitelist_local=$(uci -q get p2p-intel.application.whitelist_local || echo "1")
|
|
if [ "$whitelist_local" = "1" ] && _is_local_ip "$value"; then
|
|
echo '{"valid": false, "reason": "local_ip_whitelisted"}'
|
|
return 1
|
|
fi
|
|
;;
|
|
|
|
domain)
|
|
# Basic domain format check
|
|
if ! echo "$value" | grep -qE '^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z]{2,})+$'; then
|
|
echo '{"valid": false, "reason": "invalid_domain_format"}'
|
|
return 1
|
|
fi
|
|
;;
|
|
|
|
url)
|
|
# Basic URL format check
|
|
if ! echo "$value" | grep -qE '^https?://'; then
|
|
echo '{"valid": false, "reason": "invalid_url_format"}'
|
|
return 1
|
|
fi
|
|
;;
|
|
|
|
hash)
|
|
# Check hash format (MD5, SHA1, SHA256)
|
|
local len=${#value}
|
|
if [ "$len" != "32" ] && [ "$len" != "40" ] && [ "$len" != "64" ]; then
|
|
echo '{"valid": false, "reason": "invalid_hash_format"}'
|
|
return 1
|
|
fi
|
|
;;
|
|
|
|
*)
|
|
echo '{"valid": false, "reason": "unknown_ioc_type"}'
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
echo '{"valid": true}'
|
|
return 0
|
|
}
|
|
|
|
# Validate signed batch
|
|
validate_batch() {
|
|
local batch="$1"
|
|
local batch_file="$2"
|
|
|
|
local require_signature
|
|
require_signature=$(uci -q get p2p-intel.validation.require_signature || echo "1")
|
|
|
|
# Verify signature if required
|
|
if [ "$require_signature" = "1" ]; then
|
|
if [ -f /usr/lib/p2p-intel/signer.sh ]; then
|
|
. /usr/lib/p2p-intel/signer.sh
|
|
if ! verify_batch_signature "$batch"; then
|
|
echo '{"valid": false, "reason": "invalid_signature"}'
|
|
return 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
local signer
|
|
signer=$(echo "$batch" | jsonfilter -e '@.signer' 2>/dev/null)
|
|
|
|
# Extract and validate each IOC
|
|
local valid_count=0
|
|
local invalid_count=0
|
|
local validated_iocs="["
|
|
local first=1
|
|
|
|
local iocs
|
|
iocs=$(echo "$batch" | jsonfilter -e '@.iocs[*]' 2>/dev/null)
|
|
|
|
echo "$iocs" | while read -r ioc; do
|
|
[ -z "$ioc" ] && continue
|
|
|
|
local result
|
|
result=$(validate_ioc "$ioc" "$signer")
|
|
|
|
if echo "$result" | grep -q '"valid": true'; then
|
|
valid_count=$((valid_count + 1))
|
|
[ "$first" = "1" ] || validated_iocs="$validated_iocs,"
|
|
validated_iocs="$validated_iocs$ioc"
|
|
first=0
|
|
else
|
|
invalid_count=$((invalid_count + 1))
|
|
fi
|
|
done
|
|
|
|
validated_iocs="$validated_iocs]"
|
|
|
|
# Save validated IOCs
|
|
local output_file="$VALIDATED_DIR/$(date +%s)_$signer.json"
|
|
echo "$validated_iocs" > "$output_file"
|
|
|
|
cat <<EOF
|
|
{
|
|
"valid": true,
|
|
"source": "$signer",
|
|
"validated": $valid_count,
|
|
"rejected": $invalid_count,
|
|
"output_file": "$output_file"
|
|
}
|
|
EOF
|
|
}
|
|
|
|
# Process received IOC package
|
|
process_received() {
|
|
local input_file="$1"
|
|
|
|
if [ ! -f "$input_file" ]; then
|
|
echo '{"error": "File not found"}'
|
|
return 1
|
|
fi
|
|
|
|
local batch
|
|
batch=$(cat "$input_file")
|
|
|
|
validate_batch "$batch" "$input_file"
|
|
}
|
|
|
|
# Get validated IOCs
|
|
get_validated() {
|
|
echo "["
|
|
local first=1
|
|
for f in "$VALIDATED_DIR"/*.json 2>/dev/null; do
|
|
[ -f "$f" ] || continue
|
|
[ "$first" = "1" ] || echo ","
|
|
cat "$f"
|
|
first=0
|
|
done
|
|
echo "]"
|
|
}
|
|
|
|
# Clean old validated/rejected files
|
|
cleanup() {
|
|
local max_age="${1:-604800}" # 7 days default
|
|
find "$VALIDATED_DIR" -type f -mtime +7 -delete 2>/dev/null
|
|
find "$REJECTED_DIR" -type f -mtime +7 -delete 2>/dev/null
|
|
find "$RECEIVED_DIR" -type f -mtime +7 -delete 2>/dev/null
|
|
}
|
|
|
|
# Initialize on source
|
|
validator_init
|