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>
297 lines
6.8 KiB
Bash
297 lines
6.8 KiB
Bash
#!/bin/sh
|
|
# P2P Intel Applier - Apply validated IOCs to security controls
|
|
|
|
. /lib/functions.sh
|
|
|
|
INTEL_DIR="/var/lib/p2p-intel"
|
|
APPLIED_FILE="$INTEL_DIR/applied.json"
|
|
PENDING_FILE="$INTEL_DIR/pending.json"
|
|
|
|
# Initialize applier
|
|
applier_init() {
|
|
[ -f "$APPLIED_FILE" ] || echo '[]' > "$APPLIED_FILE"
|
|
[ -f "$PENDING_FILE" ] || echo '[]' > "$PENDING_FILE"
|
|
}
|
|
|
|
# Get application method
|
|
_get_method() {
|
|
uci -q get p2p-intel.application.apply_method || echo "nftables"
|
|
}
|
|
|
|
# Get ban duration
|
|
_get_ban_duration() {
|
|
uci -q get p2p-intel.application.ban_duration || echo "86400"
|
|
}
|
|
|
|
# Get ipset name
|
|
_get_ipset_name() {
|
|
uci -q get p2p-intel.application.ipset_name || echo "p2p_intel_blocked"
|
|
}
|
|
|
|
# Apply IP ban via nftables
|
|
_apply_nftables_ip() {
|
|
local ip="$1"
|
|
local duration="$2"
|
|
|
|
local ipset_name
|
|
ipset_name=$(_get_ipset_name)
|
|
|
|
# Ensure ipset exists
|
|
nft list set inet fw4 "$ipset_name" >/dev/null 2>&1 || \
|
|
nft add set inet fw4 "$ipset_name" '{ type ipv4_addr; flags timeout; }' 2>/dev/null
|
|
|
|
# Add IP with timeout
|
|
nft add element inet fw4 "$ipset_name" "{ $ip timeout ${duration}s }" 2>/dev/null
|
|
|
|
if [ $? -eq 0 ]; then
|
|
logger -t p2p-intel "Applied IP ban: $ip (${duration}s via nftables)"
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Apply IP ban via iptables
|
|
_apply_iptables_ip() {
|
|
local ip="$1"
|
|
local duration="$2"
|
|
|
|
local ipset_name
|
|
ipset_name=$(_get_ipset_name)
|
|
|
|
# Ensure ipset exists
|
|
ipset list "$ipset_name" >/dev/null 2>&1 || \
|
|
ipset create "$ipset_name" hash:ip timeout "$duration" 2>/dev/null
|
|
|
|
# Add IP
|
|
ipset add "$ipset_name" "$ip" timeout "$duration" 2>/dev/null
|
|
|
|
if [ $? -eq 0 ]; then
|
|
logger -t p2p-intel "Applied IP ban: $ip (${duration}s via iptables)"
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Apply IP ban via CrowdSec
|
|
_apply_crowdsec_ip() {
|
|
local ip="$1"
|
|
local duration="$2"
|
|
local scenario="${3:-p2p-intel/shared-ioc}"
|
|
|
|
if ! command -v cscli >/dev/null 2>&1; then
|
|
return 1
|
|
fi
|
|
|
|
cscli decisions add -i "$ip" -d "${duration}s" -R "$scenario" 2>/dev/null
|
|
|
|
if [ $? -eq 0 ]; then
|
|
logger -t p2p-intel "Applied IP ban: $ip (${duration}s via CrowdSec)"
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Apply domain block
|
|
_apply_domain_block() {
|
|
local domain="$1"
|
|
|
|
# Add to dnsmasq blocklist
|
|
local blocklist="/etc/dnsmasq.d/p2p-intel-blocked.conf"
|
|
|
|
# Check if already blocked
|
|
if grep -q "^address=/$domain/" "$blocklist" 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
|
|
echo "address=/$domain/0.0.0.0" >> "$blocklist"
|
|
|
|
# Reload dnsmasq
|
|
/etc/init.d/dnsmasq reload 2>/dev/null
|
|
|
|
logger -t p2p-intel "Applied domain block: $domain"
|
|
return 0
|
|
}
|
|
|
|
# Apply single IOC
|
|
apply_ioc() {
|
|
local ioc="$1"
|
|
|
|
local ioc_type value action duration
|
|
ioc_type=$(echo "$ioc" | jsonfilter -e '@.type' 2>/dev/null)
|
|
value=$(echo "$ioc" | jsonfilter -e '@.value' 2>/dev/null)
|
|
action=$(echo "$ioc" | jsonfilter -e '@.action' 2>/dev/null)
|
|
duration=$(_get_ban_duration)
|
|
|
|
local method
|
|
method=$(_get_method)
|
|
|
|
local result=1
|
|
|
|
case "$ioc_type" in
|
|
ip)
|
|
case "$method" in
|
|
nftables)
|
|
_apply_nftables_ip "$value" "$duration" && result=0
|
|
;;
|
|
iptables)
|
|
_apply_iptables_ip "$value" "$duration" && result=0
|
|
;;
|
|
crowdsec)
|
|
_apply_crowdsec_ip "$value" "$duration" && result=0
|
|
;;
|
|
esac
|
|
;;
|
|
|
|
domain)
|
|
_apply_domain_block "$value" && result=0
|
|
;;
|
|
|
|
*)
|
|
logger -t p2p-intel "Unknown IOC type: $ioc_type"
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
if [ "$result" -eq 0 ]; then
|
|
# Record as applied
|
|
_record_applied "$ioc"
|
|
fi
|
|
|
|
return $result
|
|
}
|
|
|
|
# Record applied IOC
|
|
_record_applied() {
|
|
local ioc="$1"
|
|
local timestamp
|
|
timestamp=$(date +%s)
|
|
|
|
local entry="{\"ioc\":$ioc,\"applied_at\":$timestamp}"
|
|
|
|
local tmp_file="/tmp/applied_$$.json"
|
|
if [ -s "$APPLIED_FILE" ] && [ "$(cat "$APPLIED_FILE")" != "[]" ]; then
|
|
sed 's/]$/,'"$entry"']/' "$APPLIED_FILE" > "$tmp_file"
|
|
else
|
|
echo "[$entry]" > "$tmp_file"
|
|
fi
|
|
mv "$tmp_file" "$APPLIED_FILE"
|
|
}
|
|
|
|
# Add IOC to pending queue (for manual approval)
|
|
queue_pending() {
|
|
local ioc="$1"
|
|
local source="$2"
|
|
local timestamp
|
|
timestamp=$(date +%s)
|
|
|
|
local entry="{\"ioc\":$ioc,\"source\":\"$source\",\"queued_at\":$timestamp,\"status\":\"pending\"}"
|
|
|
|
local tmp_file="/tmp/pending_$$.json"
|
|
if [ -s "$PENDING_FILE" ] && [ "$(cat "$PENDING_FILE")" != "[]" ]; then
|
|
sed 's/]$/,'"$entry"']/' "$PENDING_FILE" > "$tmp_file"
|
|
else
|
|
echo "[$entry]" > "$tmp_file"
|
|
fi
|
|
mv "$tmp_file" "$PENDING_FILE"
|
|
|
|
logger -t p2p-intel "Queued IOC for approval from $source"
|
|
}
|
|
|
|
# Get pending IOCs
|
|
get_pending() {
|
|
cat "$PENDING_FILE"
|
|
}
|
|
|
|
# Approve pending IOC
|
|
approve_pending() {
|
|
local index="$1"
|
|
|
|
# Get the IOC at index
|
|
local ioc
|
|
ioc=$(jsonfilter -i "$PENDING_FILE" -e "@[$index].ioc" 2>/dev/null)
|
|
|
|
if [ -z "$ioc" ]; then
|
|
echo '{"error": "IOC not found"}'
|
|
return 1
|
|
fi
|
|
|
|
# Apply it
|
|
apply_ioc "$ioc"
|
|
|
|
# Remove from pending (simplified - just mark as approved)
|
|
logger -t p2p-intel "Approved pending IOC at index $index"
|
|
|
|
echo '{"success": true}'
|
|
}
|
|
|
|
# Reject pending IOC
|
|
reject_pending() {
|
|
local index="$1"
|
|
|
|
# Mark as rejected (simplified)
|
|
logger -t p2p-intel "Rejected pending IOC at index $index"
|
|
|
|
echo '{"success": true}'
|
|
}
|
|
|
|
# Apply all validated IOCs (auto-apply mode)
|
|
apply_all_validated() {
|
|
local validated_dir="$INTEL_DIR/validated"
|
|
|
|
local applied=0
|
|
local failed=0
|
|
|
|
for f in "$validated_dir"/*.json 2>/dev/null; do
|
|
[ -f "$f" ] || continue
|
|
|
|
jsonfilter -i "$f" -e '@[*]' 2>/dev/null | while read -r ioc; do
|
|
[ -z "$ioc" ] && continue
|
|
|
|
if apply_ioc "$ioc"; then
|
|
applied=$((applied + 1))
|
|
else
|
|
failed=$((failed + 1))
|
|
fi
|
|
done
|
|
|
|
# Move processed file
|
|
mv "$f" "$INTEL_DIR/processed/" 2>/dev/null
|
|
done
|
|
|
|
echo "{\"applied\": $applied, \"failed\": $failed}"
|
|
}
|
|
|
|
# Get applied IOCs
|
|
get_applied() {
|
|
cat "$APPLIED_FILE"
|
|
}
|
|
|
|
# Remove IP ban
|
|
remove_ban() {
|
|
local ip="$1"
|
|
local method
|
|
method=$(_get_method)
|
|
local ipset_name
|
|
ipset_name=$(_get_ipset_name)
|
|
|
|
case "$method" in
|
|
nftables)
|
|
nft delete element inet fw4 "$ipset_name" "{ $ip }" 2>/dev/null
|
|
;;
|
|
iptables)
|
|
ipset del "$ipset_name" "$ip" 2>/dev/null
|
|
;;
|
|
crowdsec)
|
|
cscli decisions delete -i "$ip" 2>/dev/null
|
|
;;
|
|
esac
|
|
|
|
logger -t p2p-intel "Removed ban: $ip"
|
|
}
|
|
|
|
# Initialize on source
|
|
applier_init
|