#!/bin/bash
# =============================================================================
# blackhole-auto.sh — Automated Blackhole Management
# =============================================================================
# Runs every 60s on Shield (414342) ONLY.
# Reads high-severity sec_events, auto-blacklists persistent offenders,
# and activates blocks via exclusion-svc container API.
#
# CRITICAL: 10.69.69.10 and WG subnets NEVER blocked.
# =============================================================================
set -euo pipefail

NODE_ID="${NODE_ID:-414342}"
TOOL_ID="blackhole-auto"

# ── DB Config ──
DB_HOST="${STORE_DB_HOST:-172.28.50.1}"
DB_PORT="${STORE_DB_PORT:-3306}"
DB_NAME="${STORE_DB_NAME:-shared_engine}"
DB_USER="${STORE_DB_USER:-tooling}"
DB_PASS="${STORE_DB_PASS:-}"

CRED_FILE="/etc/binary/tooling.env"
[ -f "$CRED_FILE" ] && source "$CRED_FILE" 2>/dev/null || true

# ── Exclusion Service ──
EXCLUSION_URL="${EXCLUSION_URL:-http://localhost:9443}"
BLACKHOLE_PORT="${BLACKHOLE_PORT:-9997}"
DRYRUN="${BLACKHOLE_DRYRUN:-0}"

# ── Whitelist (ABSOLUTE — never block these) ──
WHITELIST=(
    "10.69.69.10"         # OPERATOR VPN — NEVER BLOCK
    "10.69.69.0/24"       # wg7 mesh
    "172.28.47.0/28"      # compass tunnel
    "172.28.41.0/30"      # lantern tunnel
    "172.23.83.0/30"      # rain tunnel
    "10.83.47.0/30"       # umbrella tunnel
    "172.29.11.0/30"      # gravel tunnel
    "127.0.0.0/8"         # loopback
)

sql_escape() { echo "$1" | sed "s/'/''/g"; }

# ── Check if IP is whitelisted ──
is_whitelisted() {
    local ip="$1"
    for entry in "${WHITELIST[@]}"; do
        if [[ "$entry" == *"/"* ]]; then
            # CIDR check
            if python3 -c "
import ipaddress, sys
try:
    sys.exit(0 if ipaddress.ip_address('$ip') in ipaddress.ip_network('$entry', strict=False) else 1)
except: sys.exit(1)
" 2>/dev/null; then
                return 0
            fi
        else
            [[ "$ip" == "$entry" ]] && return 0
        fi
    done
    return 1
}

# ── Query Store DB ──
db_query() {
    MYSQL_PWD="$DB_PASS" mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -D"$DB_NAME" \
        --batch --skip-column-names -e "$1" 2>/dev/null
}

db_write() {
    [[ -z "${DB_PASS:-}" ]] && return 0
    { MYSQL_PWD="$DB_PASS" mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -D"$DB_NAME" \
        --connect-timeout=3 -e "$1" 2>/dev/null; } &
    disown 2>/dev/null
}

# ── Call exclusion-svc API ──
call_exclusion() {
    local ip="$1" reason="$2" duration="${3:-86400}"
    
    if [[ "$DRYRUN" == "1" ]]; then
        echo "  [DRYRUN] Would block $ip via exclusion-svc"
        return 0
    fi
    
    local response
    response=$(curl -sf --max-time 5 -X POST "${EXCLUSION_URL}/api/block" \
        -H "Content-Type: application/json" \
        -d "{\"ip\":\"$ip\",\"reason\":\"$reason\",\"duration\":$duration}" \
        2>/dev/null) || true
    
    if [[ -n "$response" ]]; then
        echo "  exclusion-svc: blocked $ip"
        return 0
    else
        echo "  exclusion-svc: unreachable (block logged in DB only)"
        return 1
    fi
}

# ── Main automation loop ──
main() {
    [[ -z "${DB_PASS:-}" ]] && { echo "ERROR: No DB credentials"; exit 1; }
    
    echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] blackhole-auto start"
    local blocked=0
    
    # 1. Fetch high-severity sec_events from last 60 seconds with IP addresses
    local events
    events=$(db_query "
        SELECT DISTINCT ip_address_enc 
        FROM sec_events 
        WHERE status_code = 1 
          AND severity IN ('high', 'critical')
          AND ip_address_enc IS NOT NULL 
          AND ip_address_enc != ''
          AND created_at >= NOW() - INTERVAL 60 SECOND
    ") || events=""
    
    [[ -z "$events" ]] && { echo "  No high-severity events"; return 0; }
    
    while IFS= read -r ip; do
        [[ -z "$ip" ]] && continue
        ip=$(echo "$ip" | tr -d '[:space:]')
        
        # WHITELIST CHECK — absolute priority
        if is_whitelisted "$ip"; then
            echo "  SKIP (whitelisted): $ip"
            continue
        fi
        
        # Check if already blacklisted
        local already
        already=$(db_query "
            SELECT COUNT(*) FROM sec_blacklist_nft 
            WHERE ip_address = '$(sql_escape "$ip")' 
              AND status_code = 1 
              AND (expires_at IS NULL OR expires_at > NOW())
        ") || already=0
        
        if [[ "$already" -gt 0 ]]; then
            continue
        fi
        
        # Count events from this IP in last 5 minutes (threshold check)
        local event_count
        event_count=$(db_query "
            SELECT COUNT(*) FROM sec_events 
            WHERE ip_address_enc = '$(sql_escape "$ip")' 
              AND status_code = 1 
              AND severity IN ('high', 'critical')
              AND created_at >= NOW() - INTERVAL 5 MINUTE
        ") || event_count=0
        
        # Require 2+ high-severity events before auto-blocking
        if [[ "$event_count" -lt 2 ]]; then
            echo "  THRESHOLD: $ip has $event_count events (need 2+)"
            continue
        fi
        
        # Get the most recent reason
        local reason
        reason=$(db_query "
            SELECT CONCAT(event_type, ': ', LEFT(details_enc, 100))
            FROM sec_events 
            WHERE ip_address_enc = '$(sql_escape "$ip")' 
              AND status_code = 1 
              AND severity IN ('high', 'critical')
            ORDER BY created_at DESC LIMIT 1
        ") || reason="auto-blocked"
        
        # Add to blacklist
        db_write "INSERT INTO sec_blacklist_nft 
            (ip_address, reason, severity, added_by, added_at, expires_at, status_code, node_id)
            VALUES ('$(sql_escape "$ip")', '$(sql_escape "$reason")', 'high',
                    'blackhole-auto', NOW(), DATE_ADD(NOW(), INTERVAL 24 HOUR), 1, '$NODE_ID')"
        
        # Call exclusion-svc
        call_exclusion "$ip" "$reason" 86400
        
        # Log the blackhole action
        db_write "INSERT INTO sec_events 
            (node_id, source_tag, event_type, severity, details_enc, ip_address_enc, status_code, created_at)
            VALUES ('$NODE_ID', 'blackhole-auto', 'ip_blackholed', 'high',
                    'Auto-blackholed after $event_count events: $(sql_escape "$reason")',
                    '$(sql_escape "$ip")', 1, NOW())"
        
        blocked=$((blocked + 1))
        echo "  BLOCKED: $ip ($event_count events, reason: $reason)"
        
    done <<< "$events"
    
    # 2. Expire old blacklist entries
    db_write "UPDATE sec_blacklist_nft SET status_code = 0 
              WHERE status_code = 1 AND expires_at IS NOT NULL AND expires_at <= NOW()"
    
    echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] blackhole-auto done blocked=$blocked"
}

main "$@"
