#!/usr/bin/env bash
set -Eeuo pipefail

CONFIG_FILE="${NICEOS_K8S_FIREWALL_CONFIG:-/etc/sysconfig/niceos-kubernetes-firewall}"

if [ -r "${CONFIG_FILE}" ]; then
  # shellcheck disable=SC1090
  . "${CONFIG_FILE}"
fi

: "${NICEOS_K8S_ENABLED:=yes}"
: "${NICEOS_K8S_PROFILE:=compat}"
: "${NICEOS_K8S_NODE_ROLE:=auto}"

: "${NICEOS_K8S_IPTABLES_BIN:=/usr/sbin/iptables}"
: "${NICEOS_K8S_IPTABLES_SAVE_BIN:=/usr/sbin/iptables-save}"
: "${NICEOS_K8S_IPTABLES_WAIT:=10}"

: "${NICEOS_K8S_JUMP_PLACEMENT:=after-kubernetes}"

: "${NICEOS_K8S_POD_CIDRS:=10.244.0.0/16}"
: "${NICEOS_K8S_SERVICE_CIDRS:=10.96.0.0/12}"
: "${NICEOS_K8S_NODE_CIDRS:=}"
: "${NICEOS_K8S_CONTROL_PLANE_CIDRS:=}"
: "${NICEOS_K8S_ADMIN_CIDRS:=}"

# If NODE_CIDRS is empty, detect the primary IPv4 interface subnet automatically.
# This is the default production behavior: worker nodes must come up without
# asking the administrator to know Kubernetes firewall internals.
: "${NICEOS_K8S_AUTO_DETECT_NODE_CIDRS:=yes}"

: "${NICEOS_K8S_LOCAL_API_PORTS:=6443}"
: "${NICEOS_K8S_API_SOURCES:=auto}"

: "${NICEOS_K8S_ALLOW_POD_TO_KUBELET:=yes}"
: "${NICEOS_K8S_LOCAL_KUBELET_PORTS:=10250}"
: "${NICEOS_K8S_KUBELET_SOURCES:=auto}"

: "${NICEOS_K8S_ALLOW_ETCD:=auto}"
: "${NICEOS_K8S_ETCD_PORTS:=2379 2380}"
: "${NICEOS_K8S_ETCD_SOURCES:=auto}"

: "${NICEOS_K8S_ALLOW_CONTROL_PLANE_HEALTH_LOCAL:=yes}"
: "${NICEOS_K8S_CONTROL_PLANE_HEALTH_PORTS:=10257 10259}"

: "${NICEOS_K8S_ALLOW_KUBE_PROXY_HEALTH:=no}"
: "${NICEOS_K8S_KUBE_PROXY_HEALTH_PORTS:=10256}"
: "${NICEOS_K8S_KUBE_PROXY_HEALTH_SOURCES:=auto}"

: "${NICEOS_K8S_ALLOW_POD_FORWARD:=yes}"
: "${NICEOS_K8S_MANAGE_OUTPUT:=yes}"

: "${NICEOS_K8S_FLANNEL_ENABLED:=yes}"
: "${NICEOS_K8S_FLANNEL_BACKEND:=vxlan}"
: "${NICEOS_K8S_FLANNEL_VXLAN_PORT:=8472}"
: "${NICEOS_K8S_FLANNEL_UDP_PORT:=8285}"
: "${NICEOS_K8S_FLANNEL_NODE_CIDRS:=auto}"

: "${NICEOS_K8S_INGRESS_ENABLED:=no}"
: "${NICEOS_K8S_INGRESS_PORTS:=80/tcp 443/tcp}"
: "${NICEOS_K8S_INGRESS_SOURCES:=0.0.0.0/0}"

: "${NICEOS_K8S_NODEPORT_MODE:=closed}"

# Default: Kubernetes cluster internals work automatically, but published
# workloads are not exposed through NodePort until explicitly allowed by
# CLI/API/automation.
: "${NICEOS_K8S_NODEPORT_ENFORCE:=yes}"
: "${NICEOS_K8S_NODEPORT_RANGE_TCP:=30000:32767}"
: "${NICEOS_K8S_NODEPORT_RANGE_UDP:=30000:32767}"
: "${NICEOS_K8S_NODEPORT_SOURCES:=0.0.0.0/0}"
: "${NICEOS_K8S_NODEPORTS:=}"

: "${NICEOS_K8S_CUSTOM_PORTS:=}"

: "${NICEOS_K8S_TERMINAL_DROP_INPUT:=no}"
: "${NICEOS_K8S_TERMINAL_DROP_FORWARD:=no}"
: "${NICEOS_K8S_TERMINAL_DROP_OUTPUT:=no}"

IN_CHAIN="NICEOS-K8S-IN"
FWD_CHAIN="NICEOS-K8S-FWD"
OUT_CHAIN="NICEOS-K8S-OUT"
NP_CHAIN="NICEOS-K8S-NODEPORT"

log() { printf '[%s] %s\n' "INFO" "$*"; }
warn() { printf '[%s] %s\n' "WARN" "$*" >&2; }
die() { printf '[%s] %s\n' "ERROR" "$*" >&2; exit 1; }

is_yes() {
  case "${1:-}" in
    yes|YES|true|TRUE|1|on|ON) return 0 ;;
    *) return 1 ;;
  esac
}

need_root() {
  [ "$(id -u)" -eq 0 ] || die "must be run as root"
}

need_iptables() {
  [ -x "${NICEOS_K8S_IPTABLES_BIN}" ] || die "iptables binary not found: ${NICEOS_K8S_IPTABLES_BIN}"
}

ipt() {
  "${NICEOS_K8S_IPTABLES_BIN}" -w "${NICEOS_K8S_IPTABLES_WAIT}" "$@"
}

words() {
  printf '%s\n' "${1:-}" | tr ',;' '  ' | xargs -r printf '%s\n'
}

chain_exists() {
  local chain="$1"
  ipt -t filter -S "${chain}" >/dev/null 2>&1
}

ensure_chain() {
  local chain="$1"
  if ! chain_exists "${chain}"; then
    ipt -t filter -N "${chain}"
  fi
  ipt -t filter -F "${chain}"
}

add_rule() {
  local chain="$1"
  shift
  if ! ipt -t filter -C "${chain}" "$@" >/dev/null 2>&1; then
    ipt -t filter -A "${chain}" "$@"
  fi
}

remove_jump_all() {
  local parent="$1"
  local chain="$2"
  while ipt -t filter -C "${parent}" -j "${chain}" >/dev/null 2>&1; do
    ipt -t filter -D "${parent}" -j "${chain}" || true
  done
}

delete_chain() {
  local chain="$1"
  if chain_exists "${chain}"; then
    ipt -t filter -F "${chain}" || true
    ipt -t filter -X "${chain}" || true
  fi
}

rule_target_is_kube_or_cni() {
  local line="$1"
  printf '%s\n' "${line}" | grep -Eq -- ' -j (KUBE-|FLANNEL-|CNI-)| -g (KUBE-|FLANNEL-|CNI-)'
}

jump_position_after_kubernetes() {
  local parent="$1"
  local idx=0
  local last=0
  local line

  while IFS= read -r line; do
    case "${line}" in
      "-A ${parent} "*)
        idx=$((idx + 1))
        if rule_target_is_kube_or_cni "${line}"; then
          last="${idx}"
        fi
        ;;
    esac
  done < <(ipt -t filter -S "${parent}" 2>/dev/null || true)

  if [ "${last}" -gt 0 ]; then
    printf '%s\n' "$((last + 1))"
  else
    printf '%s\n' "1"
  fi
}

ensure_jump_position() {
  local parent="$1"
  local chain="$2"
  local placement="${3:-after-kubernetes}"
  local pos="1"

  remove_jump_all "${parent}" "${chain}"

  case "${placement}" in
    first)
      pos="1"
      ipt -t filter -I "${parent}" "${pos}" -j "${chain}"
      ;;
    append)
      ipt -t filter -A "${parent}" -j "${chain}"
      ;;
    after-kubernetes)
      pos="$(jump_position_after_kubernetes "${parent}")"
      ipt -t filter -I "${parent}" "${pos}" -j "${chain}"
      ;;
    *)
      die "invalid NICEOS_K8S_JUMP_PLACEMENT=${placement}; expected first, after-kubernetes or append"
      ;;
  esac
}

insert_jump_before_target() {
  local parent="$1"
  local chain="$2"
  local target="$3"
  local idx=0
  local pos=1
  local found=0
  local line

  remove_jump_all "${parent}" "${chain}"

  while IFS= read -r line; do
    case "${line}" in
      "-A ${parent} "*)
        idx=$((idx + 1))
        if printf '%s\n' "${line}" | grep -Eq -- " -j ${target}( |$)| -g ${target}( |$)"; then
          pos="${idx}"
          found=1
          break
        fi
        ;;
    esac
  done < <(ipt -t filter -S "${parent}" 2>/dev/null || true)

  if [ "${found}" -eq 0 ]; then
    pos=1
  fi

  ipt -t filter -I "${parent}" "${pos}" -j "${chain}"
}

normalize_port_token() {
  local token="$1"
  local port proto
  if printf '%s' "${token}" | grep -q '/'; then
    port="${token%/*}"
    proto="${token##*/}"
  else
    port="${token}"
    proto="tcp"
  fi

  case "${proto}" in
    tcp|udp) ;;
    *) die "invalid protocol in port token '${token}', expected tcp or udp" ;;
  esac

  port="$(printf '%s' "${port}" | tr '-' ':')"
  printf '%s/%s\n' "${port}" "${proto}"
}

port_part() { printf '%s\n' "$1" | cut -d/ -f1; }
proto_part() { printf '%s\n' "$1" | cut -d/ -f2; }

cidr_list_or_auto() {
  local value="${1:-}"
  local fallback="${2:-}"

  if [ "${value}" = "auto" ]; then
    printf '%s\n' "${fallback}"
  else
    printf '%s\n' "${value}"
  fi
}

detect_role() {
  if [ "${NICEOS_K8S_NODE_ROLE}" != "auto" ]; then
    printf '%s\n' "${NICEOS_K8S_NODE_ROLE}"
    return 0
  fi

  if [ -f /etc/kubernetes/manifests/kube-apiserver.yaml ]; then
    printf '%s\n' "control-plane"
  else
    printf '%s\n' "worker"
  fi
}

detect_primary_ipv4_cidr() {
  local dev=""
  local cidr=""

  if command -v ip >/dev/null 2>&1; then
    dev="$(ip -o route get 1.1.1.1 2>/dev/null | awk '{for (i=1;i<=NF;i++) if ($i=="dev") {print $(i+1); exit}}' || true)"

    if [ -n "${dev}" ]; then
      cidr="$(ip -o -4 addr show dev "${dev}" scope global 2>/dev/null | awk 'NR==1 {print $4}' || true)"
    fi

    if [ -z "${cidr}" ]; then
      cidr="$(ip -o -4 addr show scope global 2>/dev/null \
        | awk '$2 !~ /^(lo|docker|cni|flannel|virbr|veth|br-|tun|tap)/ {print $4; exit}' || true)"
    fi
  fi

  printf '%s
' "${cidr}"
}

auto_detect_node_cidrs() {
  local detected=""

  if [ -n "${NICEOS_K8S_NODE_CIDRS}" ]; then
    return 0
  fi

  if ! is_yes "${NICEOS_K8S_AUTO_DETECT_NODE_CIDRS}"; then
    return 0
  fi

  detected="$(detect_primary_ipv4_cidr)"

  if [ -n "${detected}" ]; then
    NICEOS_K8S_NODE_CIDRS="${detected}"

    if [ -z "${NICEOS_K8S_CONTROL_PLANE_CIDRS}" ]; then
      NICEOS_K8S_CONTROL_PLANE_CIDRS="${detected}"
    fi

    if [ -z "${NICEOS_K8S_ADMIN_CIDRS}" ]; then
      NICEOS_K8S_ADMIN_CIDRS="${detected}"
    fi
  else
    warn "could not auto-detect node CIDR; set NICEOS_K8S_NODE_CIDRS explicitly if multi-node CNI traffic is blocked"
  fi
}

auto_detect_cluster_values() {
  local value

  if [ -f /etc/kubernetes/manifests/kube-controller-manager.yaml ]; then
    value="$(grep -Eo -- '--cluster-cidr=[^ ,]+' /etc/kubernetes/manifests/kube-controller-manager.yaml 2>/dev/null | head -1 | cut -d= -f2- || true)"
    if [ -n "${value}" ] && [ "${NICEOS_K8S_POD_CIDRS}" = "10.244.0.0/16" ]; then
      NICEOS_K8S_POD_CIDRS="${value}"
    fi
  fi

  if [ -f /etc/kubernetes/manifests/kube-apiserver.yaml ]; then
    value="$(grep -Eo -- '--service-cluster-ip-range=[^ ,]+' /etc/kubernetes/manifests/kube-apiserver.yaml 2>/dev/null | head -1 | cut -d= -f2- || true)"
    if [ -n "${value}" ] && [ "${NICEOS_K8S_SERVICE_CIDRS}" = "10.96.0.0/12" ]; then
      NICEOS_K8S_SERVICE_CIDRS="${value}"
    fi

    value="$(grep -Eo -- '--secure-port=[0-9]+' /etc/kubernetes/manifests/kube-apiserver.yaml 2>/dev/null | head -1 | cut -d= -f2- || true)"
    if [ -n "${value}" ] && [ "${NICEOS_K8S_LOCAL_API_PORTS}" = "6443" ]; then
      NICEOS_K8S_LOCAL_API_PORTS="${value}"
    fi
  fi
}

add_accept_src_dport() {
  local chain="$1"
  local source="$2"
  local token="$3"
  local comment="$4"
  local norm proto dport

  norm="$(normalize_port_token "${token}")"
  dport="$(port_part "${norm}")"
  proto="$(proto_part "${norm}")"

  if [ -n "${source}" ] && [ "${source}" != "0.0.0.0/0" ] && [ "${source}" != "any" ]; then
    add_rule "${chain}" -s "${source}" -p "${proto}" -m "${proto}" --dport "${dport}" \
      -m comment --comment "${comment}" -j ACCEPT
  else
    add_rule "${chain}" -p "${proto}" -m "${proto}" --dport "${dport}" \
      -m comment --comment "${comment}" -j ACCEPT
  fi
}

add_accept_dst_dport() {
  local chain="$1"
  local dest="$2"
  local token="$3"
  local comment="$4"
  local norm proto dport

  norm="$(normalize_port_token "${token}")"
  dport="$(port_part "${norm}")"
  proto="$(proto_part "${norm}")"

  if [ -n "${dest}" ] && [ "${dest}" != "0.0.0.0/0" ] && [ "${dest}" != "any" ]; then
    add_rule "${chain}" -d "${dest}" -p "${proto}" -m "${proto}" --dport "${dport}" \
      -m comment --comment "${comment}" -j ACCEPT
  else
    add_rule "${chain}" -p "${proto}" -m "${proto}" --dport "${dport}" \
      -m comment --comment "${comment}" -j ACCEPT
  fi
}

add_accept_src_all() {
  local chain="$1"
  local source="$2"
  local comment="$3"
  add_rule "${chain}" -s "${source}" -m comment --comment "${comment}" -j ACCEPT
}

add_accept_dst_all() {
  local chain="$1"
  local dest="$2"
  local comment="$3"
  add_rule "${chain}" -d "${dest}" -m comment --comment "${comment}" -j ACCEPT
}

apply_input_rules() {
  local role="$1"
  local api_sources kubelet_sources etcd_sources flannel_sources health_sources
  local cidr port token source

  add_rule "${IN_CHAIN}" -i lo -m comment --comment "NiceOS Kubernetes: allow loopback input" -j ACCEPT
  add_rule "${IN_CHAIN}" -m conntrack --ctstate RELATED,ESTABLISHED \
    -m comment --comment "NiceOS Kubernetes: allow established input" -j ACCEPT
  add_rule "${IN_CHAIN}" -m conntrack --ctstate INVALID \
    -m comment --comment "NiceOS Kubernetes: drop invalid input" -j DROP

  if [ "${role}" = "control-plane" ] || [ "${role}" = "single" ]; then
    api_sources="$(cidr_list_or_auto "${NICEOS_K8S_API_SOURCES}" "${NICEOS_K8S_NODE_CIDRS} ${NICEOS_K8S_CONTROL_PLANE_CIDRS} ${NICEOS_K8S_ADMIN_CIDRS}")"

    for cidr in $(words "${api_sources}"); do
      for port in $(words "${NICEOS_K8S_LOCAL_API_PORTS}"); do
        add_accept_src_dport "${IN_CHAIN}" "${cidr}" "${port}/tcp" \
          "NiceOS Kubernetes: allow kube-apiserver"
      done
    done

    for cidr in $(words "${NICEOS_K8S_POD_CIDRS}"); do
      for port in $(words "${NICEOS_K8S_LOCAL_API_PORTS}"); do
        add_accept_src_dport "${IN_CHAIN}" "${cidr}" "${port}/tcp" \
          "NiceOS Kubernetes: allow pod CIDR to local kube-apiserver"
      done
    done

    if is_yes "${NICEOS_K8S_ALLOW_CONTROL_PLANE_HEALTH_LOCAL}"; then
      for port in $(words "${NICEOS_K8S_CONTROL_PLANE_HEALTH_PORTS}"); do
        add_rule "${IN_CHAIN}" -i lo -p tcp -m tcp --dport "${port}" \
          -m comment --comment "NiceOS Kubernetes: allow local control-plane health port" -j ACCEPT
      done
    fi

    if [ "${NICEOS_K8S_ALLOW_ETCD}" = "yes" ] || { [ "${NICEOS_K8S_ALLOW_ETCD}" = "auto" ] && [ -n "${NICEOS_K8S_CONTROL_PLANE_CIDRS}${NICEOS_K8S_NODE_CIDRS}" ]; }; then
      etcd_sources="$(cidr_list_or_auto "${NICEOS_K8S_ETCD_SOURCES}" "${NICEOS_K8S_CONTROL_PLANE_CIDRS} ${NICEOS_K8S_NODE_CIDRS}")"
      for cidr in $(words "${etcd_sources}"); do
        for port in $(words "${NICEOS_K8S_ETCD_PORTS}"); do
          add_accept_src_dport "${IN_CHAIN}" "${cidr}" "${port}/tcp" \
            "NiceOS Kubernetes: allow etcd control-plane traffic"
        done
      done
    fi
  fi

  if is_yes "${NICEOS_K8S_ALLOW_POD_TO_KUBELET}"; then
    kubelet_sources="$(cidr_list_or_auto "${NICEOS_K8S_KUBELET_SOURCES}" "${NICEOS_K8S_POD_CIDRS} ${NICEOS_K8S_NODE_CIDRS} ${NICEOS_K8S_CONTROL_PLANE_CIDRS}")"
    for cidr in $(words "${kubelet_sources}"); do
      for port in $(words "${NICEOS_K8S_LOCAL_KUBELET_PORTS}"); do
        add_accept_src_dport "${IN_CHAIN}" "${cidr}" "${port}/tcp" \
          "NiceOS Kubernetes: allow kubelet secure port"
      done
    done
  fi

  if is_yes "${NICEOS_K8S_ALLOW_KUBE_PROXY_HEALTH}"; then
    health_sources="$(cidr_list_or_auto "${NICEOS_K8S_KUBE_PROXY_HEALTH_SOURCES}" "${NICEOS_K8S_NODE_CIDRS} ${NICEOS_K8S_CONTROL_PLANE_CIDRS}")"
    for cidr in $(words "${health_sources}"); do
      for port in $(words "${NICEOS_K8S_KUBE_PROXY_HEALTH_PORTS}"); do
        add_accept_src_dport "${IN_CHAIN}" "${cidr}" "${port}/tcp" \
          "NiceOS Kubernetes: allow kube-proxy health port"
      done
    done
  fi

  if is_yes "${NICEOS_K8S_FLANNEL_ENABLED}"; then
    flannel_sources="$(cidr_list_or_auto "${NICEOS_K8S_FLANNEL_NODE_CIDRS}" "${NICEOS_K8S_NODE_CIDRS}")"
    case "${NICEOS_K8S_FLANNEL_BACKEND}" in
      vxlan)
        for cidr in $(words "${flannel_sources}"); do
          add_accept_src_dport "${IN_CHAIN}" "${cidr}" "${NICEOS_K8S_FLANNEL_VXLAN_PORT}/udp" \
            "NiceOS Kubernetes: allow flannel vxlan"
        done
        ;;
      udp)
        for cidr in $(words "${flannel_sources}"); do
          add_accept_src_dport "${IN_CHAIN}" "${cidr}" "${NICEOS_K8S_FLANNEL_UDP_PORT}/udp" \
            "NiceOS Kubernetes: allow flannel udp backend"
        done
        ;;
      none|disabled|no)
        ;;
      *)
        warn "unknown NICEOS_K8S_FLANNEL_BACKEND=${NICEOS_K8S_FLANNEL_BACKEND}; no flannel port was added"
        ;;
    esac
  fi

  if is_yes "${NICEOS_K8S_INGRESS_ENABLED}"; then
    for source in $(words "${NICEOS_K8S_INGRESS_SOURCES}"); do
      for token in $(words "${NICEOS_K8S_INGRESS_PORTS}"); do
        add_accept_src_dport "${IN_CHAIN}" "${source}" "${token}" \
          "NiceOS Kubernetes: expose ingress"
      done
    done
  fi

  for token in $(words "${NICEOS_K8S_CUSTOM_PORTS}"); do
    # Format: PORT[/proto]:SOURCE[/mask]
    local port_token="${token%%:*}"
    local src_token="${token#*:}"
    [ "${src_token}" != "${token}" ] || src_token="0.0.0.0/0"
    add_accept_src_dport "${IN_CHAIN}" "${src_token}" "${port_token}" \
      "NiceOS Kubernetes: allow custom port"
  done

  if is_yes "${NICEOS_K8S_TERMINAL_DROP_INPUT}"; then
    add_rule "${IN_CHAIN}" -m comment --comment "NiceOS Kubernetes: terminal input drop" -j DROP
  fi
}

apply_forward_rules() {
  local cidr

  add_rule "${FWD_CHAIN}" -m conntrack --ctstate INVALID \
    -m comment --comment "NiceOS Kubernetes: drop invalid forwarded packets" -j DROP
  add_rule "${FWD_CHAIN}" -m conntrack --ctstate RELATED,ESTABLISHED \
    -m comment --comment "NiceOS Kubernetes: allow established forwarding" -j ACCEPT

  if is_yes "${NICEOS_K8S_ALLOW_POD_FORWARD}"; then
    for cidr in $(words "${NICEOS_K8S_POD_CIDRS}"); do
      add_accept_src_all "${FWD_CHAIN}" "${cidr}" \
        "NiceOS Kubernetes: allow pod CIDR forwarding source"
      add_accept_dst_all "${FWD_CHAIN}" "${cidr}" \
        "NiceOS Kubernetes: allow pod CIDR forwarding destination"
    done

    for cidr in $(words "${NICEOS_K8S_SERVICE_CIDRS}"); do
      add_accept_dst_all "${FWD_CHAIN}" "${cidr}" \
        "NiceOS Kubernetes: allow service CIDR forwarding destination"
    done
  fi

  if is_yes "${NICEOS_K8S_TERMINAL_DROP_FORWARD}"; then
    add_rule "${FWD_CHAIN}" -m comment --comment "NiceOS Kubernetes: terminal forward drop" -j DROP
  fi
}

apply_output_rules() {
  local cidr port flannel_dests

  add_rule "${OUT_CHAIN}" -o lo -m comment --comment "NiceOS Kubernetes: allow loopback output" -j ACCEPT
  add_rule "${OUT_CHAIN}" -m conntrack --ctstate RELATED,ESTABLISHED \
    -m comment --comment "NiceOS Kubernetes: allow established output" -j ACCEPT
  add_rule "${OUT_CHAIN}" -m conntrack --ctstate INVALID \
    -m comment --comment "NiceOS Kubernetes: drop invalid output" -j DROP

  for cidr in $(words "${NICEOS_K8S_POD_CIDRS}"); do
    add_accept_dst_all "${OUT_CHAIN}" "${cidr}" \
      "NiceOS Kubernetes: allow host output to pod CIDR"
  done

  for cidr in $(words "${NICEOS_K8S_SERVICE_CIDRS}"); do
    add_accept_dst_all "${OUT_CHAIN}" "${cidr}" \
      "NiceOS Kubernetes: allow host output to service CIDR"
  done

  for cidr in $(words "${NICEOS_K8S_NODE_CIDRS}"); do
    add_accept_dst_all "${OUT_CHAIN}" "${cidr}" \
      "NiceOS Kubernetes: allow host output to node CIDR"
  done

  for port in $(words "${NICEOS_K8S_LOCAL_API_PORTS}"); do
    add_accept_dst_dport "${OUT_CHAIN}" "" "${port}/tcp" \
      "NiceOS Kubernetes: allow host output to kube-apiserver port"
  done

  if is_yes "${NICEOS_K8S_FLANNEL_ENABLED}"; then
    flannel_dests="$(cidr_list_or_auto "${NICEOS_K8S_FLANNEL_NODE_CIDRS}" "${NICEOS_K8S_NODE_CIDRS}")"
    case "${NICEOS_K8S_FLANNEL_BACKEND}" in
      vxlan)
        for cidr in $(words "${flannel_dests}"); do
          add_accept_dst_dport "${OUT_CHAIN}" "${cidr}" "${NICEOS_K8S_FLANNEL_VXLAN_PORT}/udp" \
            "NiceOS Kubernetes: allow host output to flannel vxlan"
        done
        ;;
      udp)
        for cidr in $(words "${flannel_dests}"); do
          add_accept_dst_dport "${OUT_CHAIN}" "${cidr}" "${NICEOS_K8S_FLANNEL_UDP_PORT}/udp" \
            "NiceOS Kubernetes: allow host output to flannel udp backend"
        done
        ;;
    esac
  fi

  if is_yes "${NICEOS_K8S_TERMINAL_DROP_OUTPUT}"; then
    add_rule "${OUT_CHAIN}" -m comment --comment "NiceOS Kubernetes: terminal output drop" -j DROP
  fi
}

apply_nodeport_gate() {
  local token source port_token src_token

  ensure_chain "${NP_CHAIN}"

  add_rule "${NP_CHAIN}" -m conntrack --ctstate RELATED,ESTABLISHED \
    -m comment --comment "NiceOS Kubernetes: nodeport established return" -j RETURN

  # Non-NodePort traffic must immediately return to INPUT.
  add_rule "${NP_CHAIN}" -p tcp -m tcp ! --dport "${NICEOS_K8S_NODEPORT_RANGE_TCP}" \
    -m comment --comment "NiceOS Kubernetes: non-tcp-nodeport return" -j RETURN
  add_rule "${NP_CHAIN}" -p udp -m udp ! --dport "${NICEOS_K8S_NODEPORT_RANGE_UDP}" \
    -m comment --comment "NiceOS Kubernetes: non-udp-nodeport return" -j RETURN

  case "${NICEOS_K8S_NODEPORT_MODE}" in
    closed|none|off)
      ;;
    range)
      for source in $(words "${NICEOS_K8S_NODEPORT_SOURCES}"); do
        add_accept_src_dport "${NP_CHAIN}" "${source}" "${NICEOS_K8S_NODEPORT_RANGE_TCP}/tcp" \
          "NiceOS Kubernetes: allow nodeport tcp range"
        add_accept_src_dport "${NP_CHAIN}" "${source}" "${NICEOS_K8S_NODEPORT_RANGE_UDP}/udp" \
          "NiceOS Kubernetes: allow nodeport udp range"
      done
      ;;
    specific)
      for token in $(words "${NICEOS_K8S_NODEPORTS}"); do
        # Format: PORT[/proto]:SOURCE[/mask]
        port_token="${token%%:*}"
        src_token="${token#*:}"
        [ "${src_token}" != "${token}" ] || src_token="0.0.0.0/0"
        add_accept_src_dport "${NP_CHAIN}" "${src_token}" "${port_token}" \
          "NiceOS Kubernetes: allow selected nodeport"
      done
      ;;
    *)
      die "invalid NICEOS_K8S_NODEPORT_MODE=${NICEOS_K8S_NODEPORT_MODE}; expected closed, specific or range"
      ;;
  esac

  if is_yes "${NICEOS_K8S_NODEPORT_ENFORCE}"; then
    add_rule "${NP_CHAIN}" -p tcp -m tcp --dport "${NICEOS_K8S_NODEPORT_RANGE_TCP}" \
      -m comment --comment "NiceOS Kubernetes: drop non-published tcp nodeport" -j DROP
    add_rule "${NP_CHAIN}" -p udp -m udp --dport "${NICEOS_K8S_NODEPORT_RANGE_UDP}" \
      -m comment --comment "NiceOS Kubernetes: drop non-published udp nodeport" -j DROP
  fi

  add_rule "${NP_CHAIN}" -m comment --comment "NiceOS Kubernetes: nodeport default return" -j RETURN

  insert_jump_before_target INPUT "${NP_CHAIN}" KUBE-NODEPORTS
}

remove_nodeport_gate() {
  remove_jump_all INPUT "${NP_CHAIN}" || true
  delete_chain "${NP_CHAIN}" || true
}

apply_rules() {
  local role

  need_root
  need_iptables

  if ! is_yes "${NICEOS_K8S_ENABLED}"; then
    warn "NiceOS Kubernetes firewall profile is disabled in ${CONFIG_FILE}"
    exit 0
  fi

  auto_detect_cluster_values
  auto_detect_node_cidrs
  role="$(detect_role)"

  ensure_chain "${IN_CHAIN}"
  ensure_chain "${FWD_CHAIN}"

  ensure_jump_position INPUT "${IN_CHAIN}" "${NICEOS_K8S_JUMP_PLACEMENT}"
  ensure_jump_position FORWARD "${FWD_CHAIN}" "${NICEOS_K8S_JUMP_PLACEMENT}"

  apply_input_rules "${role}"
  apply_forward_rules

  if is_yes "${NICEOS_K8S_MANAGE_OUTPUT}"; then
    ensure_chain "${OUT_CHAIN}"
    ensure_jump_position OUTPUT "${OUT_CHAIN}" "${NICEOS_K8S_JUMP_PLACEMENT}"
    apply_output_rules
  else
    remove_jump_all OUTPUT "${OUT_CHAIN}" || true
    delete_chain "${OUT_CHAIN}" || true
  fi

  if is_yes "${NICEOS_K8S_NODEPORT_ENFORCE}" || [ "${NICEOS_K8S_NODEPORT_MODE}" = "specific" ] || [ "${NICEOS_K8S_NODEPORT_MODE}" = "range" ]; then
    apply_nodeport_gate
  else
    remove_nodeport_gate
  fi

  log "Applied NiceOS Kubernetes firewall profile"
  log "Profile: ${NICEOS_K8S_PROFILE}"
  log "Detected role: ${role}"
  log "Jump placement: ${NICEOS_K8S_JUMP_PLACEMENT}"
  log "Pod CIDRs: ${NICEOS_K8S_POD_CIDRS}"
  log "Service CIDRs: ${NICEOS_K8S_SERVICE_CIDRS}"
  log "Node CIDRs: ${NICEOS_K8S_NODE_CIDRS:-none}"
  log "API sources: ${NICEOS_K8S_API_SOURCES}"
  log "Flannel: ${NICEOS_K8S_FLANNEL_ENABLED}/${NICEOS_K8S_FLANNEL_BACKEND}"
  log "NodePort mode: ${NICEOS_K8S_NODEPORT_MODE}, enforce: ${NICEOS_K8S_NODEPORT_ENFORCE}"
  log "Manage OUTPUT: ${NICEOS_K8S_MANAGE_OUTPUT}"
}

delete_rules() {
  need_root
  need_iptables

  remove_jump_all INPUT "${NP_CHAIN}" || true
  remove_jump_all INPUT "${IN_CHAIN}" || true
  remove_jump_all FORWARD "${FWD_CHAIN}" || true
  remove_jump_all OUTPUT "${OUT_CHAIN}" || true

  delete_chain "${NP_CHAIN}" || true
  delete_chain "${IN_CHAIN}" || true
  delete_chain "${FWD_CHAIN}" || true
  delete_chain "${OUT_CHAIN}" || true

  log "Removed NiceOS Kubernetes firewall profile"
}

status_rules() {
  need_iptables

  echo "=== NiceOS Kubernetes firewall config ==="
  echo "CONFIG_FILE=${CONFIG_FILE}"
  echo "NICEOS_K8S_ENABLED=${NICEOS_K8S_ENABLED}"
  echo "NICEOS_K8S_PROFILE=${NICEOS_K8S_PROFILE}"
  echo "NICEOS_K8S_NODE_ROLE=${NICEOS_K8S_NODE_ROLE}"
  echo "NICEOS_K8S_JUMP_PLACEMENT=${NICEOS_K8S_JUMP_PLACEMENT}"
  echo "NICEOS_K8S_POD_CIDRS=${NICEOS_K8S_POD_CIDRS}"
  echo "NICEOS_K8S_SERVICE_CIDRS=${NICEOS_K8S_SERVICE_CIDRS}"
  echo "NICEOS_K8S_NODE_CIDRS=${NICEOS_K8S_NODE_CIDRS}"
  echo "NICEOS_K8S_AUTO_DETECT_NODE_CIDRS=${NICEOS_K8S_AUTO_DETECT_NODE_CIDRS}"
  echo "NICEOS_K8S_CONTROL_PLANE_CIDRS=${NICEOS_K8S_CONTROL_PLANE_CIDRS}"
  echo "NICEOS_K8S_ADMIN_CIDRS=${NICEOS_K8S_ADMIN_CIDRS}"
  echo "NICEOS_K8S_LOCAL_API_PORTS=${NICEOS_K8S_LOCAL_API_PORTS}"
  echo "NICEOS_K8S_API_SOURCES=${NICEOS_K8S_API_SOURCES}"
  echo "NICEOS_K8S_ALLOW_POD_TO_KUBELET=${NICEOS_K8S_ALLOW_POD_TO_KUBELET}"
  echo "NICEOS_K8S_LOCAL_KUBELET_PORTS=${NICEOS_K8S_LOCAL_KUBELET_PORTS}"
  echo "NICEOS_K8S_ALLOW_POD_FORWARD=${NICEOS_K8S_ALLOW_POD_FORWARD}"
  echo "NICEOS_K8S_MANAGE_OUTPUT=${NICEOS_K8S_MANAGE_OUTPUT}"
  echo "NICEOS_K8S_FLANNEL_ENABLED=${NICEOS_K8S_FLANNEL_ENABLED}"
  echo "NICEOS_K8S_FLANNEL_BACKEND=${NICEOS_K8S_FLANNEL_BACKEND}"
  echo "NICEOS_K8S_NODEPORT_MODE=${NICEOS_K8S_NODEPORT_MODE}"
  echo "NICEOS_K8S_NODEPORT_ENFORCE=${NICEOS_K8S_NODEPORT_ENFORCE}"

  echo
  echo "=== filter policies ==="
  ipt -t filter -S INPUT | head -1 || true
  ipt -t filter -S FORWARD | head -1 || true
  ipt -t filter -S OUTPUT | head -1 || true

  echo
  echo "=== top-chain NiceOS/Kubernetes order ==="
  ipt -t filter -S INPUT | grep -E 'KUBE-|FLANNEL-|CNI-|NICEOS-K8S' || true
  echo
  ipt -t filter -S FORWARD | grep -E 'KUBE-|FLANNEL-|CNI-|NICEOS-K8S' || true
  echo
  ipt -t filter -S OUTPUT | grep -E 'KUBE-|FLANNEL-|CNI-|NICEOS-K8S' || true

  for chain in "${NP_CHAIN}" "${IN_CHAIN}" "${FWD_CHAIN}" "${OUT_CHAIN}"; do
    echo
    echo "=== ${chain} ==="
    ipt -t filter -L "${chain}" -n -v --line-numbers 2>/dev/null || true
  done
}

diagnose_rules() {
  status_rules

  echo
  echo "=== Kubernetes diagnostics ==="
  if command -v kubectl >/dev/null 2>&1; then
    if [ -r /etc/kubernetes/admin.conf ]; then
      export KUBECONFIG="${KUBECONFIG:-/etc/kubernetes/admin.conf}"
    fi
    kubectl get nodes -o wide 2>/dev/null || true
    echo
    kubectl get svc -A -o wide 2>/dev/null || true
    echo
    kubectl get endpoints kubernetes -o wide 2>/dev/null || true
    echo
    kubectl -n kube-system get pods -o wide 2>/dev/null || true
  else
    warn "kubectl not found; Kubernetes diagnostics skipped"
  fi

  echo
  echo "=== NAT service rules for Kubernetes API ==="
  ipt -t nat -L KUBE-SERVICES -n -v --line-numbers 2>/dev/null | grep -E '10\.96\.0\.1|kubernetes:https' || true
  ipt -t nat -L KUBE-SVC-NPX46M4PTMTKRN6Y -n -v --line-numbers 2>/dev/null || true
}

usage() {
  cat <<'USAGE'
Usage:
  niceos-kubernetes-firewall apply
  niceos-kubernetes-firewall reload
  niceos-kubernetes-firewall delete
  niceos-kubernetes-firewall status
  niceos-kubernetes-firewall diagnose

Config file:
  /etc/sysconfig/niceos-kubernetes-firewall

Important variables:
  NICEOS_K8S_NODE_ROLE=auto|control-plane|worker|single
  NICEOS_K8S_AUTO_DETECT_NODE_CIDRS=yes
  NICEOS_K8S_NODE_CIDRS="192.168.10.0/24"
  NICEOS_K8S_ADMIN_CIDRS="192.168.10.0/24"
  NICEOS_K8S_API_SOURCES=auto
  NICEOS_K8S_FLANNEL_ENABLED=yes
  NICEOS_K8S_FLANNEL_BACKEND=vxlan
  NICEOS_K8S_FLANNEL_VXLAN_PORT=8472
  NICEOS_K8S_NODEPORT_MODE=closed|specific|range
  NICEOS_K8S_NODEPORT_ENFORCE=no|yes
  NICEOS_K8S_NODEPORTS="30080/tcp:192.168.10.0/24"
  NICEOS_K8S_INGRESS_ENABLED=no|yes
USAGE
}

cmd="${1:-status}"
case "${cmd}" in
  apply)
    apply_rules
    ;;
  reload|restart)
    delete_rules
    apply_rules
    ;;
  delete|remove|cleanup)
    delete_rules
    ;;
  status)
    status_rules
    ;;
  diagnose|diag)
    diagnose_rules
    ;;
  help|-h|--help)
    usage
    ;;
  *)
    usage
    exit 2
    ;;
esac
