/root/bin/mk-iptables-conf-fw.sh (2)

From RaySoft
#!/bin/bash -
#-------------------------------------------------------------------------------
# mk-iptables-conf-fw.sh
# ======================
#
# Project   Gentoo 4 Shuttle DS57Ux
# Scope     Linux
# Copyright (C) 2022 by RaySoft, Zurich, Switzerland
# License   GNU General Public License (GPL) 2.0
#           https://www.gnu.org/licenses/gpl2.txt
#
#-------------------------------------------------------------------------------
#
# http://ipset.netfilter.org/iptables.man.html
# http://ipset.netfilter.org/ip6tables.man.html
#
# http://ipset.netfilter.org/iptables-extensions.man.html
#
# http://www.iana.org/assignments/icmp-parameters
# http://www.iana.org/assignments/icmpv6-parameters
#
#-------------------------------------------------------------------------------

set -o 'noglob' -o 'pipefail' # -o 'xtrace' -o 'errexit'

#-------------------------------------------------------------------------------
# Variables
# ---------

IF_WAN='enp2s0'
IF_LAN='eno1'
IF_LO='lo'

SIP_PROVIDER='XXX.XXX.XXX.XXX/24'

#-------------------------------------------------------------------------------
# Define binaries
# ---------------

BASENAME=('/usr/bin/basename')
IPTABLES=('/sbin/iptables')
IP6TABLES=('/sbin/ip6tables')
SYSCTL=('/usr/sbin/sysctl')

BINS=("${BASENAME[0]}" "${IPTABLES[0]}" "${IP6TABLES[0]}" "${SYSCTL[0]}")

#-------------------------------------------------------------------------------
# Carp (Library: My)
# ------------------

my::carp() {
  local action=()

  if [[ "$1" =~ ^-e([0-9]*)$ ]]; then
    action=('exit' "${BASH_REMATCH[1]:-1}"); shift
  fi

  echo -n "$("${BASENAME[@]}" "$0")[${BASH_LINENO[-2]}]" 1>&2

  for ((i = ${#FUNCNAME[@]}-2; i >= 1; i--)); do
    echo -n ">${FUNCNAME[${i}]}[${BASH_LINENO[$((i - 1))]}]" 1>&2
  done

  echo ": $*" 1>&2

  if [[ "${action[0]}" == 'exit' ]]; then
    "${action[@]}"
  fi
}

#-------------------------------------------------------------------------------
# iptables (Library: iptables)
# ----------------------------

it::iptables() {
  local target="$1"; shift

  local func_info="${FUNCNAME[1]}[${BASH_LINENO[0]}]"
  local log_info="iptables (${func_info}:${target}): "

  if [[ "$1" == 'log' ]]; then
    shift

    if ! "${IPTABLES[@]}" "$@" \
           --match 'limit' --limit '1/second' \
           --jump 'LOG' --log-level 'info' --log-prefix "${log_info}"; then
      my::carp -e 'Error executing iptables'
    fi
  fi

  if ! "${IPTABLES[@]}" "$@" \
         --match 'comment' --comment "${func_info}" \
         --jump "${target}"; then
    my::carp -e 'Error executing iptables'
  fi
}

#-------------------------------------------------------------------------------
# ip6tables (Library: iptables)
# -----------------------------

it::ip6tables() {
  local target="$1"; shift

  local func_info="${FUNCNAME[1]}[${BASH_LINENO[0]}]"
  local log_info="iptables (${func_info}:${target}): "

  if [[ "$1" == 'log' ]]; then
    shift

    if ! "${IP6TABLES[@]}" "$@" \
           --match 'limit' --limit '1/second' \
           --jump 'LOG' --log-level 'info' --log-prefix "${log_info}"; then
      my::carp -e 'Error executing iptables'
    fi
  fi

  if ! "${IP6TABLES[@]}" "$@" \
         --match 'comment' --comment "${func_info}" \
         --jump "${target}"; then
    my::carp -e 'Error executing iptables'
  fi
}

#-------------------------------------------------------------------------------
# Reset (Library: iptables)
# -------------------------

it::reset() {
  echo -n 'IPv4: Reset policies & rules in table:'

  for table in 'raw' 'filter' 'nat'; do
    echo -n " ${table},"

    if ! "${IPTABLES[@]}" --table "${table}" --flush; then
      my::carp -e 'Error executing iptables'
    fi

    if ! "${IPTABLES[@]}" --table "${table}" --delete-chain; then
      my::carp -e 'Error executing iptables'
    fi
  done

  echo
  echo -n 'IPv6: Reset policies & rules in table:'

  for table in 'filter'; do
    echo -n " ${table},"

    if ! "${IP6TABLES[@]}" --table "${table}" --flush; then
      my::carp -e 'Error executing iptables'
    fi

    if ! "${IP6TABLES[@]}" --table "${table}" --delete-chain; then
      my::carp -e 'Error executing iptables'
    fi
  done

  echo
}

#-------------------------------------------------------------------------------
# Defaults (Library: iptables)
# ----------------------------

it::defaults() {
  echo -n 'IPv4: Set default policy & rules:'

  for chain in 'INPUT' 'FORWARD' 'OUTPUT'; do
    echo -n " ${chain},"

    # Set default policy to DROP
    if ! "${IPTABLES[@]}" --policy "${chain}" 'DROP'; then
      my::carp -e 'Error executing iptables'
    fi

    # Allow all known (ESTABLISHED or RELATED) connections
    it::iptables 'ACCEPT' \
      --append "${chain}" \
      --match 'conntrack' --ctstate 'ESTABLISHED,RELATED'
  done

  echo
  echo -n 'IPv6: Set default policy & rules:'

  for chain in 'INPUT' 'FORWARD' 'OUTPUT'; do
    echo -n " ${chain},"

    # Set default policy to DROP
    if ! "${IP6TABLES[@]}" --policy "${chain}" 'DROP'; then
      my::carp -e 'Error executing iptables'
    fi
  done

  echo
}

#-------------------------------------------------------------------------------
# Bad packets (Library: iptables)
# -------------------------------

it::bad_pkgs() {
  echo 'IPv4: Block bad packets'

  # Anti-spoofing
  it::iptables 'DROP' \
    --table 'raw' --append 'PREROUTING' \
    --match 'rpfilter' --invert

  for chain in 'INPUT' 'FORWARD'; do
    # Block all invalid packets
    it::iptables 'DROP' \
      --append "${chain}" \
      --match 'conntrack' --ctstate 'INVALID'

    # Block new TCP sessions which are not starting with a SYN packet
    it::iptables 'DROP' \
      --append "${chain}" \
      --protocol 'tcp' ! --syn \
      --match 'conntrack' --ctstate 'NEW'

    # Protect against 'SYN floods'
    if ! "${IPTABLES[@]}" --append "${chain}" \
           --protocol 'tcp' --syn \
           --match 'conntrack' --ctstate 'NEW' \
           --match 'recent' --set --name 'synflood' \
           --match 'comment' --comment "${FUNCNAME}_${LINENO}"; then
      my::carp -e 'Error executing iptables'
    fi
    it::iptables 'DROP' 'log' \
      --append "${chain}" \
      --protocol 'tcp' --syn \
      --match 'conntrack' --ctstate 'NEW' \
      --match 'recent' --update --name 'synflood' --seconds 1 --hitcount 50

    # Block TCP Bogus packets (TCP flags 64 & 128)
    for option in 64 128; do
      it::iptables 'DROP' 'log' \
        --append "${chain}" \
        --protocol 'tcp' --tcp-option "${option}"
    done

    # Protect against 'smurf attacks'
    for type in 'address-mask-request' 'timestamp-request'; do
      it::iptables 'DROP' 'log' \
        --append "${chain}" \
        --protocol 'icmp' --icmp-type "${type}"
    done

    # Block fragmented ICMP packets ('fragmentation flag' is not 0)
    it::iptables 'DROP' 'log' \
      --append "${chain}" \
      --protocol 'icmp' \
      --match 'u32' ! --u32 '4&0x1FFF=0'

    # Block oversized, unfragmented ICMP packets
    it::iptables 'DROP' 'log' \
      --append "${chain}" \
      --protocol 'icmp' \
      --match 'length' --length '1492:65535'
  done
}

#-------------------------------------------------------------------------------
# Loopback (Library: iptables)
# ----------------------------

it::loopback() {
  echo 'IPv4: Set Loopback rules'

  it::iptables 'ACCEPT' \
    --append 'INPUT' \
    --in-interface "${IF_LO}" \
    --match 'conntrack' --ctstate 'NEW'

  it::iptables 'ACCEPT' \
    --append 'OUTPUT' \

  echo 'IPv6: Set Loopback rules'

  it::ip6tables 'ACCEPT' \
    --append 'INPUT' \
    --in-interface "${IF_LO}" \
    --match 'conntrack' --ctstate 'NEW'

  it::ip6tables 'ACCEPT' \
    --append 'OUTPUT' \
    --out-interface "${IF_LO}" \
    --match 'conntrack' --ctstate 'NEW'
}

#-------------------------------------------------------------------------------
# From LAN to Firewall (Library: iptables)
# ----------------------------------------

it::lan2fw() {
  local tcp_ports='domain,ssh'
  local udp_ports='bootpc,bootps,domain,ntp'

  local defaults=('--append' 'INPUT' \
                  '--in-interface' "${IF_LAN}" \
                  '--match' 'conntrack' '--ctstate' 'NEW')

  echo 'IPv4: Set LAN to Firewall rules:'

  echo '- Drop annoying UDP packets'
  it::iptables 'DROP' "${defaults[@]}" \
    --protocol 'udp' \
    --match 'multiport' --destination-ports '2190,7423'

  echo '- Allow ICMP'
  it::iptables 'ACCEPT' "${defaults[@]}" \
    --protocol 'icmp'

  echo "- Allow TCP: ${tcp_ports}"
  it::iptables 'ACCEPT' \
    "${defaults[@]}" \
    --protocol 'tcp' --syn \
    --match 'multiport' --destination-ports "${tcp_ports}"

  echo "- Allow UDP: ${udp_ports}"
  it::iptables 'ACCEPT' "${defaults[@]}" \
    --protocol 'udp' \
    --match 'multiport' --destination-ports "${udp_ports}"

  echo '- Log & drop'
  it::iptables 'DROP' 'log' "${defaults[@]}"
}

#-------------------------------------------------------------------------------
# From LAN to WAN (Library: iptables)
# -----------------------------------

it::lan2wan() {
  local tcp_ports='ftp,http,https,imaps,nicname,rsync,smtp,ssh,submission'
  local udp_ports='ntp,5060'

  local defaults=('--append' 'FORWARD' \
                  '--in-interface' "${IF_LAN}" '--out-interface' "${IF_WAN}" \
                  '--match' 'conntrack' '--ctstate' 'NEW')

  echo 'IPv4: Set LAN to Firewall rules:'

  echo '- Setup Masquerade'
  it::iptables 'MASQUERADE' \
    --table 'nat' --append 'POSTROUTING' \
    --out-interface "${IF_WAN}"

  echo '- Drop annoying TCP packets'
  it::iptables 'DROP' "${defaults[@]}" \
    --protocol 'tcp' --syn \
    --match 'multiport' --destination-ports '5222,5223'

  echo '- Allow ICMP'
  it::iptables 'ACCEPT' "${defaults[@]}" \
    --protocol 'icmp'

  echo "- Allow TCP: ${tcp_ports}"
  it::iptables 'ACCEPT' "${defaults[@]}" \
    --protocol 'tcp' --syn \
    --match 'multiport' --destination-ports "${tcp_ports}"

  echo "- Allow UDP: ${udp_ports}"
  it::iptables 'ACCEPT' "${defaults[@]}" \
    --protocol 'udp' \
    --match 'multiport' --destination-ports "${udp_ports}"

  it::iptables 'ACCEPT' "${defaults[@]}" \
    --destination "${SIP_PROVIDER}" \
    --protocol 'udp' \
    --match 'multiport' --destination-ports '1024:65535'

  echo '- Log & drop'
  it::iptables 'DROP' 'log' "${defaults[@]}"
}

#-------------------------------------------------------------------------------
# From Firewall to LAN (Library: iptables)
# ----------------------------------------

it::fw2lan() {
  local defaults=('--append' 'INPUT' \
                  '--out-interface' "${IF_LAN}" \
                  '--match' 'conntrack' '--ctstate' 'NEW')

  echo 'IPv4: Set Firewall to LAN rules:'

  echo '- Log & drop'
  it::iptables 'DROP' 'log' "${defaults[@]}"
}

#-------------------------------------------------------------------------------
# From Firewall to WAN (Library: iptables)
# ----------------------------------------

it::fw2wan() {
  local tcp_ports='domain,http,https,rsync,submission'
  local udp_ports='bootpc,bootps,domain,ntp'

  local defaults=('--append' 'OUTPUT' \
                  '--out-interface' "${IF_WAN}" \
                  '--match' 'conntrack' '--ctstate' 'NEW')

  echo 'IPv4: Set Firewall to WAN rules:'

  echo '- Allow ICMP'
  it::iptables 'ACCEPT' "${defaults[@]}" \
    --protocol 'icmp'

  echo "- Allow TCP: ${tcp_ports}"
  it::iptables 'ACCEPT' "${defaults[@]}" \
    --protocol 'tcp' --syn \
    --match 'multiport' --destination-ports "${tcp_ports}"

  echo "- Allow UDP: ${udp_ports}"
  it::iptables 'ACCEPT' "${defaults[@]}" \
    --protocol 'udp' \
    --match 'multiport' --destination-ports "${udp_ports}"

  echo '- Log & drop'
  it::iptables 'DROP' 'log' "${defaults[@]}"
}

#-------------------------------------------------------------------------------
# From WAN to LAN (Library: iptables)
# -----------------------------------

it::wan2lan() {
  local defaults=('--append' 'FORWARD' \
                  '--in-interface' "${IF_WAN}" '--out-interface' "${IF_LAN}" \
                  '--match' 'conntrack' '--ctstate' 'NEW')

  echo 'IPv4: Set WAN to LAN rules:'

  echo '- Log & drop'
  it::iptables 'DROP' 'log' "${defaults[@]}"
}

#-------------------------------------------------------------------------------
# From WAN to Firewall (Library: iptables)
# ----------------------------------------

it::wan2fw() {
  local defaults=('--append' 'OUTPUT' \
                  '--in-interface' "${IF_WAN}" \
                  '--match' 'conntrack' '--ctstate' 'NEW')

  echo 'IPv4: Set WAN to Firewall rules:'

  echo '- Log & drop'
  it::iptables 'DROP' 'log' "${defaults[@]}"
}

#-------------------------------------------------------------------------------
# List all (Library: iptables)
# ----------------------------

it::list_all() {
  for table in 'raw' 'filter' 'nat'; do
    echo
    echo "IPv4: Rules on table ${table}"

    if ! "${IPTABLES[@]}" --table "${table}" --verbose --line-numbers --list; then
      my::carp -e 'Error executing iptables'
    fi
  done

  for table in 'filter'; do
    echo
    echo "IPv6: Rules on table ${table}"

    if ! "${IP6TABLES[@]}" --table "${table}" --verbose --line-numbers --list; then
      my::carp -e 'Error executing iptables'
    fi
  done
}

#-------------------------------------------------------------------------------
# MAIN
# ----

# Test binaries

echo -n 'Main: Test if binaries are available:'

for bin in "${BINS[@]}"; do
  echo -n " ${bin},"

  if ! type -P "${bin}" >'/dev/null' 2>&1; then
    echo
    my_carp -e "Error finding binary: ${bin}"
  fi
done

echo

# Test kernel flags

echo -n 'IPv4: Test if kernel flags are set:'

for flag in 'ip_dynaddr' 'ip_forward' 'conf.all.rp_filter' \
            'conf.default.rp_filter'; do
  ipv4_flag="net.ipv4.${flag}"

  echo -n " ${ipv4_flag},"

  if [[ $("${SYSCTL[@]}" -n "${ipv4_flag}" 2>'/dev/null') -eq 0 ]]; then
    echo
    my::carp -e "Error finding kernel flag: ${ipv4_flag}"
  fi
done

echo

it::reset
it::defaults
it::bad_pkgs
it::loopback
it::lan2fw
it::lan2wan
# it::fw2lan
it::fw2wan
# it::wan2lan
# it::wan2fw

it::list_all

#-------------------------------------------------------------------------------

exit 0

Usage