#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

allowed_domains_file="/etc/codex/allowed_domains.txt"
include_github_meta_ranges="${CODEX_INCLUDE_GITHUB_META_RANGES:-1}"

if [ -f "$allowed_domains_file" ]; then
  mapfile -t allowed_domains < <(sed '/^\s*#/d;/^\s*$/d' "$allowed_domains_file")
else
  allowed_domains=("api.openai.com")
fi

if [ "${#allowed_domains[@]}" -eq 0 ]; then
  echo "ERROR: No allowed domains configured"
  exit 1
fi

add_ipv4_cidr_to_allowlist() {
  local source="$1"
  local cidr="$2"

  if [[ ! "$cidr" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}/[0-9]{1,2}$ ]]; then
    echo "ERROR: Invalid ${source} CIDR range: $cidr"
    exit 1
  fi

  ipset add allowed-domains "$cidr" -exist
}

configure_ipv6_default_deny() {
  if ! command -v ip6tables >/dev/null 2>&1; then
    echo "ERROR: ip6tables is required to enforce IPv6 default-deny policy"
    exit 1
  fi

  ip6tables -F
  ip6tables -X
  ip6tables -t mangle -F
  ip6tables -t mangle -X
  ip6tables -t nat -F 2>/dev/null || true
  ip6tables -t nat -X 2>/dev/null || true

  ip6tables -A INPUT -i lo -j ACCEPT
  ip6tables -A OUTPUT -o lo -j ACCEPT
  ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
  ip6tables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

  ip6tables -P INPUT DROP
  ip6tables -P FORWARD DROP
  ip6tables -P OUTPUT DROP

  echo "IPv6 firewall policy configured (default-deny)"
}

# Preserve docker-managed DNS NAT rules before clearing tables.
docker_dns_rules="$(iptables-save -t nat | grep "127\\.0\\.0\\.11" || true)"

iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
ipset destroy allowed-domains 2>/dev/null || true

if [ -n "$docker_dns_rules" ]; then
  echo "Restoring Docker DNS NAT rules"
  iptables -t nat -N DOCKER_OUTPUT 2>/dev/null || true
  iptables -t nat -N DOCKER_POSTROUTING 2>/dev/null || true
  while IFS= read -r rule; do
    [ -z "$rule" ] && continue
    iptables -t nat $rule
  done <<< "$docker_dns_rules"
fi

# Allow DNS resolution and localhost communication.
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
iptables -A INPUT -p udp --sport 53 -j ACCEPT
iptables -A INPUT -p tcp --sport 53 -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

ipset create allowed-domains hash:net

for domain in "${allowed_domains[@]}"; do
  echo "Resolving $domain"
  ips="$(dig +short A "$domain" | sed '/^\s*$/d')"
  if [ -z "$ips" ]; then
    echo "ERROR: Failed to resolve $domain"
    exit 1
  fi

  while IFS= read -r ip; do
    if [[ ! "$ip" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]]; then
      echo "ERROR: Invalid IPv4 address from DNS for $domain: $ip"
      exit 1
    fi
    ipset add allowed-domains "$ip" -exist
  done <<< "$ips"
done

if [ "$include_github_meta_ranges" = "1" ]; then
  echo "Fetching GitHub meta ranges"
  github_meta="$(curl -fsSL --connect-timeout 10 https://api.github.com/meta)"

  if ! echo "$github_meta" | jq -e '.web and .api and .git' >/dev/null; then
    echo "ERROR: GitHub meta response missing expected fields"
    exit 1
  fi

  while IFS= read -r cidr; do
    [ -z "$cidr" ] && continue
    if [[ "$cidr" == *:* ]]; then
      # Current policy enforces IPv4-only ipset entries.
      continue
    fi
    add_ipv4_cidr_to_allowlist "GitHub" "$cidr"
  done < <(echo "$github_meta" | jq -r '((.web // []) + (.api // []) + (.git // []))[]' | sort -u)
fi

host_ip="$(ip route | awk '/default/ {print $3; exit}')"
if [ -z "$host_ip" ]; then
  echo "ERROR: Failed to detect host IP"
  exit 1
fi

host_network="$(echo "$host_ip" | sed 's/\.[0-9]*$/.0\/24/')"
iptables -A INPUT -s "$host_network" -j ACCEPT
iptables -A OUTPUT -d "$host_network" -j ACCEPT

iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT DROP

iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m set --match-set allowed-domains dst -j ACCEPT

# Reject rather than silently drop to make policy failures obvious.
iptables -A INPUT -j REJECT --reject-with icmp-admin-prohibited
iptables -A OUTPUT -j REJECT --reject-with icmp-admin-prohibited
iptables -A FORWARD -j REJECT --reject-with icmp-admin-prohibited

configure_ipv6_default_deny

echo "Firewall configuration complete"

if curl --connect-timeout 5 https://example.com >/dev/null 2>&1; then
  echo "ERROR: Firewall verification failed - was able to reach https://example.com"
  exit 1
fi

if ! curl --connect-timeout 5 https://api.openai.com >/dev/null 2>&1; then
  echo "ERROR: Firewall verification failed - unable to reach https://api.openai.com"
  exit 1
fi

if [ "$include_github_meta_ranges" = "1" ] && ! curl --connect-timeout 5 https://api.github.com/zen >/dev/null 2>&1; then
  echo "ERROR: Firewall verification failed - unable to reach https://api.github.com"
  exit 1
fi

if curl --connect-timeout 5 -6 https://example.com >/dev/null 2>&1; then
  echo "ERROR: Firewall verification failed - was able to reach https://example.com over IPv6"
  exit 1
fi

echo "Firewall verification passed"
