#!/bin/bash
# svclock — Hardened lockdown manager with tiered firewall rules
set -euo pipefail
shopt -s inherit_errexit 2>/dev/null || true
IFS=$'\n\t'

readonly NODE_ID="${NODE_ID:-}"
readonly ENGINE_IP="${ENGINE_IP:-10.69.69.6}"
readonly VAULT_KEY_FILE="${VAULT_KEY_FILE:-/dev/shm/vault.key}"
readonly LOCK_STATE="/run/se/state/lockdown"
readonly LOG_DIR="/run/se/logs"
readonly CONF="/run/se/.nodeconf"
readonly LOCK_TIER="${LOCK_TIER:-low}"
readonly LOCK_FILE="/run/se/.svclock.lock"

if [[ -z "${NODE_ID}" ]]; then printf 'ERR: NODE_ID not set\n' >&2; exit 1; fi

exec 300>"${LOCK_FILE}"
if ! flock -n 300; then printf 'ERR: svclock already running\n' >&2; exit 1; fi

t005_loadconf() {
    if [[ -f "${CONF}" ]]; then
        local perms
        perms="$(stat -c '%a' "${CONF}" 2>/dev/null || printf '%s' '000')"
        [[ "${perms}" == "600" || "${perms}" == "400" ]] && source "${CONF}"
    fi
}

t005_log() {
    local m="${1:-}"
    logger -t "lock-${NODE_ID}" "${m}" 2>/dev/null || true
    mkdir -p "${LOG_DIR}"
    printf '%s %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "${m}" >> "${LOG_DIR}/lockdown.log"
}

t005_report() {
    local ev="${1:-}"
    local ts n body sig_input s
    ts="$(date +%s)"
    n="$(openssl rand -hex 16)"
    body="{\"node\":\"${NODE_ID}\",\"event\":\"${ev}\"}"
    sig_input="${ts}.${n}.POST./a/security/event.${body}"
    if [[ -f "${VAULT_KEY_FILE}" ]]; then
        s="$(printf '%s' "${sig_input}" | openssl dgst -sha256 -hmac "$(cat "${VAULT_KEY_FILE}")" -hex | awk '{print $NF}')"
    else
        s='none'
    fi
    curl -sf --connect-timeout 3 --max-time 10 \
        -X POST "http://${ENGINE_IP}:8301/a/security/event" \
        -H 'Content-Type: application/json' \
        -H "x-engine-ts: ${ts}" \
        -H "x-engine-nonce: ${n}" \
        -H "x-engine-sig: ${s}" \
        -H "x-engine-node: ${NODE_ID}" \
        -d "${body}" \
        2>/dev/null || true
}

# ── Firewall rules ─────────────────────────────────────────────────────────
t005_rules_low() {
    nft -f - <<'NFT'
table inet filter {
chain input { type filter hook input priority 0; policy accept;
icmp type echo-request accept
tcp dport { 22, 80, 443, 8301 } accept
ip saddr 10.69.69.0/24 accept
ct state established,related accept
}
chain forward { type filter hook forward priority 0; policy accept; }
chain output { type filter hook output priority 0; policy accept; }
}
NFT
}

t005_rules_medium() {
    nft -f - <<'NFT'
table inet filter {
chain input { type filter hook input priority 0; policy drop;
icmp type echo-request limit rate 5/second accept
tcp dport { 22, 443 } ct state new limit rate 10/minute accept
tcp dport 8301 accept
ip saddr 10.69.69.0/24 accept
ct state established,related accept
}
chain forward { type filter hook forward priority 0; policy drop; }
chain output { type filter hook output priority 0; policy accept; }
}
NFT
}

t005_rules_high() {
    nft -f - <<'NFT'
table inet filter {
chain input { type filter hook input priority 0; policy drop;
icmp type echo-request limit rate 2/second accept
tcp dport 22 ip saddr 10.69.69.0/24 ct state new limit rate 3/minute accept
tcp dport 443 ip saddr 10.69.69.0/24 accept
tcp dport 8301 ip saddr 10.69.69.0/24 accept
ct state established,related accept
}
chain forward { type filter hook forward priority 0; policy drop; }
chain output { type filter hook output priority 0; policy accept;
udp dport 53 accept
tcp dport { 443, 8301 } accept
ip daddr 10.69.69.0/24 accept
}
}
NFT
}

t005_rules_critical() {
    nft -f - <<'NFT'
table inet filter {
chain input { type filter hook input priority 0; policy drop;
icmp type echo-request drop
tcp dport 8301 ip saddr 10.69.69.6 accept
ct state established,related accept
}
chain forward { type filter hook forward priority 0; policy drop; }
chain output { type filter hook output priority 0; policy drop;
udp dport 53 accept
tcp dport 8301 ip daddr 10.69.69.6 accept
}
}
NFT
}

t005_apply() {
    t005_loadconf
    local tier
    tier="${1:-${LOCK_TIER}}"
    case "${tier}" in
        low)      t005_rules_low ;;
        medium)   t005_rules_medium ;;
        high)     t005_rules_high ;;
        critical) t005_rules_critical ;;
        *)        t005_rules_low; tier='low' ;;
    esac
    printf '%s' "${tier}" > "${LOCK_STATE}.tier"
    t005_log "lockdown tier=${tier}"
    t005_report "lockdown:${tier}"
}

t005_init() {
    mkdir -p "${LOG_DIR}" "$(dirname "${LOCK_STATE}")"
    printf '%s' 'low' > "${LOCK_STATE}.tier"
    t005_log 'lockdown init'
}

t005_start() {
    local t
    t="$(cat "${LOCK_STATE}.tier" 2>/dev/null || printf '%s' 'low')"
    t005_apply "${t}"
    t005_log 'lockdown started'
}

t005_stop() {
    nft flush ruleset 2>/dev/null || true
    t005_log 'lockdown stopped'
}

t005_status() {
    cat "${LOCK_STATE}.tier" 2>/dev/null || printf '%s\n' 'unknown'
}

t005_run() {
    t005_apply "${LOCK_TIER}"
}

cleanup_lock() { flock -u 300 2>/dev/null || true; }
trap cleanup_lock EXIT

case "${1:-}" in
    init)   t005_init ;;
    start)  t005_start ;;
    stop)   t005_stop ;;
    status) t005_status ;;
    run)    t005_run ;;
    *)      printf 'usage: %s init|start|stop|status|run\n' "$0"; exit 1 ;;
esac
