#!/usr/bin/env bash
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
# Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
# Source-Disclosed License — All rights reserved except as expressly granted.
# See LICENCE-CMSD-1.0.md for terms.

# SecuBox-Deb :: secubox-waf-cs-bridge-setup
# Phase 7.A (#498) — wire the mitm WAF to CrowdSec LAPI so detections
# turn into nft drops within seconds.
#
# Runs on the HOST (where cscli + crowdsec live). Outputs the TOML config
# the WAF must read. Operator then copies that TOML into the LXC where
# the WAF runs (or mounts it via lxc.mount.entry).

set -euo pipefail
readonly MODULE="secubox-waf-cs-bridge-setup"
readonly VERSION="0.1.0"

MACHINE_NAME="${MACHINE_NAME:-secubox-waf}"
LXC_BRIDGE_IP="${LXC_BRIDGE_IP:-10.100.0.1}"
LAPI_PORT="${LAPI_PORT:-8080}"
OUT_FILE="${OUT_FILE:-/tmp/crowdsec.toml}"

log() { printf '[%s] %s\n' "$MODULE" "$*" >&2; }
err() { printf '[%s] ERROR: %s\n' "$MODULE" "$*" >&2; exit 1; }

command -v cscli >/dev/null || err "cscli not found — CrowdSec not installed?"

# Step 1 — register secubox-waf as a MACHINE (watcher), not a bouncer.
# Reason : POST /v1/alerts (which is how we push decisions) requires watcher
# JWT auth, not bouncer X-Api-Key. Bouncers can only GET /v1/decisions/stream.
# Note : we also keep the legacy threats.log path (already wired via
# crowdsec/secubox-waf.yaml) as a redundant backup.

# The JSON output uses key 'machineId', not 'name'.
if cscli machines list -o json 2>/dev/null | grep -q "\"machineId\": *\"${MACHINE_NAME}\""; then
    log "machine ${MACHINE_NAME} already exists — re-creating to get a fresh password"
    cscli machines delete "${MACHINE_NAME}" >/dev/null 2>&1 || true
fi

# Generate a random password and register the machine.
# cscli writes to local_api_credentials.yaml by default — we don't want
# that file overwritten (the local agent uses it). Use -f - to send the
# creds YAML to stdout, then parse the password back out.
MACHINE_PASSWORD=$(openssl rand -base64 32 | tr -d '\n=/' | head -c 40)
[ -n "${MACHINE_PASSWORD}" ] || err "openssl rand failed"

cscli machines add "${MACHINE_NAME}" \
    --password "${MACHINE_PASSWORD}" \
    -f - >/dev/null 2>&1 || \
    err "cscli machines add failed (try cscli machines delete ${MACHINE_NAME} first)"

log "machine ${MACHINE_NAME} registered (password length: ${#MACHINE_PASSWORD})"


# Step 2 — make CrowdSec LAPI reachable from the mitmproxy LXC.
# CrowdSec binds 127.0.0.1:8080 by default (and the local agent + crowdsec-
# firewall-bouncer all expect that — we DO NOT want to change it because
# moving LAPI breaks the in-host trust chain).
# Instead we install a tiny socat forwarder on ${LXC_BRIDGE_IP}:${LAPI_PORT}
# that tunnels to 127.0.0.1:${LAPI_PORT}. Only the host's LXCs can reach
# 10.100.0.1 (it's the bridge gateway, not exposed to LAN/WAN).
SOCAT_BIN=$(command -v socat || true)
[ -n "$SOCAT_BIN" ] || err "socat not found — apt install socat"

SOCAT_UNIT=/etc/systemd/system/secubox-cs-bridge.service
if [ ! -f "$SOCAT_UNIT" ]; then
    log "installing socat forwarder ${LXC_BRIDGE_IP}:${LAPI_PORT} → 127.0.0.1:${LAPI_PORT}"
    cat > "$SOCAT_UNIT" <<EOF
[Unit]
Description=SecuBox CrowdSec LAPI bridge (LXC -> host loopback) [Phase 7.A #498]
After=crowdsec.service
Requires=crowdsec.service

[Service]
Type=simple
ExecStart=${SOCAT_BIN} TCP-LISTEN:${LAPI_PORT},bind=${LXC_BRIDGE_IP},fork,reuseaddr TCP:127.0.0.1:${LAPI_PORT}
Restart=on-failure
RestartSec=3

[Install]
WantedBy=multi-user.target
EOF
    systemctl daemon-reload
    systemctl enable --now secubox-cs-bridge.service >/dev/null
    log "secubox-cs-bridge.service started"
else
    log "secubox-cs-bridge.service already present — left as-is"
fi

# Step 3 — write TOML config for the WAF
cat > "${OUT_FILE}" <<EOF
# Generated by ${MODULE} v${VERSION} on $(date -u +%Y-%m-%dT%H:%M:%SZ)
# Phase 7.A (#498) — bridge mitm WAF detections to CrowdSec via /v1/alerts.
enabled          = true
url              = "http://${LXC_BRIDGE_IP}:${LAPI_PORT}"
machine_id       = "${MACHINE_NAME}"
machine_password = "${MACHINE_PASSWORD}"
duration         = "4h"
EOF

chmod 0600 "${OUT_FILE}"

log "config written to ${OUT_FILE} (mode 0600)"
cat <<EOF

────────────────────────────────────────────────────────────────────────
NEXT STEPS (operator) :

1. Copy ${OUT_FILE} into the WAF environment at /etc/secubox/waf/crowdsec.toml :

   # if WAF runs on host :
   sudo install -d -m 0750 -o secubox -g secubox /etc/secubox/waf
   sudo install -m 0640 -o secubox -g secubox ${OUT_FILE} /etc/secubox/waf/crowdsec.toml

   # if WAF runs in LXC 'mitmproxy' :
   sudo lxc-attach -n mitmproxy -- mkdir -p /etc/secubox/waf
   sudo cp ${OUT_FILE} /var/lib/lxc/mitmproxy/rootfs/etc/secubox/waf/crowdsec.toml
   sudo lxc-attach -n mitmproxy -- chown root:root /etc/secubox/waf/crowdsec.toml
   sudo lxc-attach -n mitmproxy -- chmod 0640 /etc/secubox/waf/crowdsec.toml

2. Restart the WAF :

   # host :
   sudo systemctl restart secubox-mitmproxy
   # LXC :
   sudo lxc-attach -n mitmproxy -- systemctl restart mitmproxy

3. Watch the bridge come online :

   journalctl -u secubox-mitmproxy -f | grep cs-bridge
   # expect : [cs-bridge] enabled — ban duration 4h via http://${LXC_BRIDGE_IP}:${LAPI_PORT}

4. Verify with a fake threat :

   curl -H 'X-Forwarded-For: 192.0.2.123' \\
        'http://${LXC_BRIDGE_IP}:8080/?test=<script>alert(1)</script>'
   # repeat BAN_THRESHOLD times, then :
   cscli decisions list --origin secubox-waf
   # expect : 192.0.2.123 banned for 4h

────────────────────────────────────────────────────────────────────────
EOF
