#!/usr/bin/env bash
# Apply the Coraza WAF mode declared in /docker/whp/settings.json.
#
# Reads `coraza_waf.mode`, expected to be one of:
#   off          — sidecar stopped/removed; haproxy-manager recreated without
#                  HAPROXY_CORAZA_SPOE_BACKEND env (no SPOE in haproxy.cfg)
#   detect_only  — sidecar running with /docker/coraza/overrides.conf bind-
#                  mounted (empty, so the baked-in enforce promotions don't
#                  activate). config.yaml's SecRuleEngine DetectionOnly keeps
#                  all rules logging-only.
#   enforce      — sidecar running with the baked-in overrides.conf
#                  (day-one enforce list: 913xxx scanner UAs, 932100-932160
#                  RCE, 933170-933200 webshells, 930120 LFI, 944100-944300
#                  Log4Shell)
#
# Always recreates haproxy-manager at the end so its template renders the
# SPOE blocks (or strips them) per the mode. Brief HAProxy outage (~3-5s);
# acceptable for an admin-triggered config change.
#
# Idempotent: safe to re-run for the same mode; skips no-op work.
# Invoked from:
#   - container-manager.sh `recreate coraza-spoa`
#   - the WHP UI's update_coraza_waf save handler
#   - the versioned installer at first deploy (to materialize the default)

set -euo pipefail

SETTINGS=/docker/whp/settings.json
CORAZA_DIR=/docker/coraza
HOST_AUDIT_DIR=/var/log/coraza
CORAZA_IMAGE=repo.anhonesthost.net/cloud-hosting-platform/coraza-spoa:latest
CONTAINER_MGR=/root/whp/scripts/container-manager.sh

if ! command -v jq >/dev/null 2>&1; then
    echo "ERROR: jq is required to parse settings.json" >&2
    exit 1
fi

if [[ ! -f "$SETTINGS" ]]; then
    echo "ERROR: $SETTINGS missing; cannot determine WAF mode" >&2
    exit 1
fi

MODE=$(jq -r '.coraza_waf.mode // "off"' "$SETTINGS")

case "$MODE" in
    off|detect_only|enforce) ;;
    *)
        echo "ERROR: invalid coraza_waf.mode '$MODE' (expected: off|detect_only|enforce)" >&2
        exit 1
        ;;
esac

echo "applying coraza_waf.mode=$MODE"

# ---- ensure host audit-log dir exists with the right ownership -----------
# distroless coraza-spoa runs as UID 65532; the host dir must be writable
# by that UID. Bind-mounted into the sidecar at /var/log/coraza.
mkdir -p "$HOST_AUDIT_DIR"
if [[ "$(stat -c %u "$HOST_AUDIT_DIR")" != "65532" ]]; then
    chown 65532:65532 "$HOST_AUDIT_DIR"
fi

mkdir -p "$CORAZA_DIR"

# ---- sidecar lifecycle ---------------------------------------------------
sidecar_running() {
    docker ps --filter name=coraza-spoa --format '{{.Names}}' | grep -q '^coraza-spoa$'
}
sidecar_exists() {
    docker ps -a --filter name=coraza-spoa --format '{{.Names}}' | grep -q '^coraza-spoa$'
}
stop_remove_sidecar() {
    if sidecar_exists; then
        docker stop coraza-spoa >/dev/null 2>&1 || true
        docker rm coraza-spoa >/dev/null 2>&1 || true
    fi
}
start_sidecar() {
    local extra_mount="${1:-}"
    docker pull "$CORAZA_IMAGE" >/dev/null
    # shellcheck disable=SC2086  # extra_mount is intentionally word-split
    docker run -d \
        --name coraza-spoa \
        --network client-net \
        --restart unless-stopped \
        --label whp.tier=edge \
        --memory=512m --memory-swap=1g \
        -v "$HOST_AUDIT_DIR:/var/log/coraza" \
        $extra_mount \
        "$CORAZA_IMAGE" >/dev/null
}

case "$MODE" in
    off)
        if sidecar_exists; then
            echo "  stopping + removing coraza-spoa"
            stop_remove_sidecar
        else
            echo "  coraza-spoa already absent"
        fi
        ;;

    detect_only)
        # Empty overrides file -> no ctl:ruleEngine=On promotions.
        # The sidecar's config.yaml has `SecRuleEngine DetectionOnly` so all
        # rules log without blocking unless overrides.conf flips specific
        # IDs to On — which it won't, because we mount an empty one.
        cat > "$CORAZA_DIR/overrides.conf" <<'EOF'
# Detect-only mode — generated by apply-coraza-waf-mode.sh.
# This file is mounted over the baked-in /etc/coraza/overrides.conf inside
# the sidecar to prevent any rule promotions. Coraza logs all matches; no
# request is blocked. Switch the WHP UI to "enforce" to use the baked-in
# day-one enforce list (913xxx scanner UAs, 932100-932160 RCE,
# 933170-933200 webshells, 930120 LFI, 944100-944300 Log4Shell).
EOF
        chmod 644 "$CORAZA_DIR/overrides.conf"
        stop_remove_sidecar
        echo "  starting coraza-spoa with empty overrides (detect-only)"
        start_sidecar "-v $CORAZA_DIR/overrides.conf:/etc/coraza/overrides.conf:ro"
        ;;

    enforce)
        # No override mount -> sidecar uses the baked-in overrides.conf
        # which promotes the day-one enforce list to ctl:ruleEngine=On.
        stop_remove_sidecar
        echo "  starting coraza-spoa with baked-in enforce list"
        start_sidecar
        ;;
esac

# ---- recreate haproxy-manager so haproxy.cfg matches the mode ------------
# recreate_haproxy() in container-manager.sh reads settings.json and adds
# the HAPROXY_CORAZA_SPOE_BACKEND env var when mode != off.
echo "  recreating haproxy-manager to apply config..."
"$CONTAINER_MGR" recreate haproxy-manager

echo "apply-coraza-waf-mode.sh: OK (mode=$MODE)"
