#!/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