#!/usr/bin/env bash
# firewall.sh -- ufw as the portable host firewall (§10).
# Reads (exported by deploy.sh): ENABLE_UFW, HTTP_PORT, HTTPS_PORT, SSH_PORT,
#   SSH_ALLOW_CIDRS (falls back to OPERATOR_ALLOW_CIDRS), OPERATOR_ALLOW_CIDRS,
#   ACCESS_MODE, REVSHELL_TARGET_CIDRS.
# Requires _common.sh.
#
# Docker-bypass caveat: Docker publishes ports via its own iptables chains and can
# bypass ufw. The loopback re-binds in the prod overlay are the REAL control for
# 3000/8090/4444; ufw here is belt-and-braces + the SSH lockdown.

_ufw_allow_sources() {
  # $1 = comma-list of CIDRs, $2 = port, $3 = proto
  local cidrs="$1" port="$2" proto="$3" cidr
  if [[ -z "${cidrs}" ]]; then
    run_sudo ufw allow "${port}/${proto}"
    return
  fi
  IFS=',' read -ra arr <<< "${cidrs}"
  for cidr in "${arr[@]}"; do
    cidr="$(echo "$cidr" | xargs)"
    [[ -z "$cidr" ]] && continue
    run_sudo ufw allow from "$cidr" to any port "$port" proto "$proto"
  done
}

setup_firewall() {
  if ! is_true "${ENABLE_UFW:-true}"; then
    disable_firewall
    return 0
  fi
  step "Firewall (ufw)"

  local ssh_port="${SSH_PORT:-22}"
  local http_port="${HTTP_PORT:-80}"
  local https_port="${HTTPS_PORT:-443}"
  local ssh_cidrs="${SSH_ALLOW_CIDRS:-}"
  [[ -z "${ssh_cidrs}" ]] && ssh_cidrs="${OPERATOR_ALLOW_CIDRS:-}"

  run_sudo ufw --force reset >/dev/null 2>&1 || true
  run_sudo ufw default deny incoming
  run_sudo ufw default allow outgoing

  # SSH: operator CIDRs only (or open if none supplied -- warn loudly)
  if [[ -z "${ssh_cidrs}" ]]; then
    warn "No SSH_ALLOW_CIDRS / OPERATOR_ALLOW_CIDRS set -- opening SSH to the world (NOT recommended)"
    run_sudo ufw allow "${ssh_port}/tcp"
  else
    info "SSH (${ssh_port}) restricted to: ${ssh_cidrs}"
    _ufw_allow_sources "${ssh_cidrs}" "${ssh_port}" tcp
  fi

  if [[ "${ACCESS_MODE:-https-domain}" == http-* ]]; then
    # http-* lab mode: the app is served on HTTP_PORT (no TLS, no ACME). Scope it to the
    # operator CIDRs when supplied (defense-in-depth on top of the nginx gate).
    if [[ -n "${OPERATOR_ALLOW_CIDRS:-}" ]]; then
      info "HTTP app (${http_port}) restricted to operator CIDRs: ${OPERATOR_ALLOW_CIDRS}"
      _ufw_allow_sources "${OPERATOR_ALLOW_CIDRS}" "${http_port}" tcp
    else
      warn "No OPERATOR_ALLOW_CIDRS -- opening HTTP app (${http_port}) to the world (nginx gate is the only brake)"
      run_sudo ufw allow "${http_port}/tcp"
    fi
  else
    # https-* : HTTP (80) stays world-open for Let's Encrypt http-01 (LE validates from
    # arbitrary IPs) and the HTTP->HTTPS redirect. HTTPS (the app) is scoped to the
    # operator CIDRs when supplied; world-open only if none given.
    run_sudo ufw allow "${http_port}/tcp"
    if [[ -n "${OPERATOR_ALLOW_CIDRS:-}" ]]; then
      info "HTTPS (${https_port}) restricted to operator CIDRs: ${OPERATOR_ALLOW_CIDRS}"
      _ufw_allow_sources "${OPERATOR_ALLOW_CIDRS}" "${https_port}" tcp
    else
      warn "No OPERATOR_ALLOW_CIDRS -- opening HTTPS (${https_port}) to the world (nginx gate is the only brake)"
      run_sudo ufw allow "${https_port}/tcp"
    fi
  fi

  # 4444 reverse-shell catcher stays CLOSED at the firewall here. The container binds it to
  # 127.0.0.1 (prod overlay), so it is not externally reachable. Per-engagement exposure is
  # handled by `./deploy.sh revshell-open`, which starts a host-side socat forwarder (a real
  # INPUT-chain listener ufw can scope) and opens 4444 to REVSHELL_TARGET_CIDRS, then
  # `revshell-close` tears it back down. See the README "Reverse shell" section.
  info "4444 stays closed here; use './deploy.sh revshell-open' per engagement"

  run_sudo ufw --force enable
  run_sudo ufw status verbose || true
  success "ufw active"
}

disable_firewall() {
  info "Firewall DISABLED (ENABLE_UFW=false) -- relying on the cloud Security Group + loopback binds"
  run_sudo ufw --force disable >/dev/null 2>&1 || true
}
