importing new rebuild

This commit is contained in:
tengel 2024-03-20 11:20:58 -05:00
parent a6ebf87180
commit 9e832a0fe3
4 changed files with 411 additions and 0 deletions

View file

@ -2,4 +2,32 @@
rebuild dynamic IPtables chain with DNS lookups of named hosts rebuild dynamic IPtables chain with DNS lookups of named hosts
### Server prep and usage:
1. Place script in /usr/local/sbin/dyniptables.sh (root:root, 0744)
2. Add to system on-boot iptables rules a new filter chain and (j)ump:
```
:DYNAMIC - [0:0]
-A INPUT -j DYNAMIC
```
...where DYNAMIC is the name of the $DCHAIN in dyniptables.conf
3. Add to root's crontab a refresh every 6 hours:
```
5 */6 * * * /usr/local/sbin/dyniptables.sh
```
4. Add an override.conf to systemd iptables startup:
```
DEB clones: `systemctl edit netfilter-persistent.service`
or
RPM clones: `systemctl edit iptables.service` (IPv4)
`systemctl edit ip6tables.service` (IPv6)
[Service]
ExecStartPost=/usr/local/sbin/dyniptables.sh # (DEB, all rules)
or
ExecStartPost=/usr/local/sbin/dyniptables.sh -4 # (RPM, IPv4 only)
ExecStartPost=/usr/local/sbin/dyniptables.sh -6 # (RPM, IPv6 only)
```
SPDX-License-Identifier: MIT SPDX-License-Identifier: MIT

32
dyniptables.conf Normal file
View file

@ -0,0 +1,32 @@
# dyniptables config file
# permissions on this file must be root:root, 0644
# The DHOSTS array is already declared; uncomment and add a line for
# each host to allow, notice '+=' to ADD to the array each line.
#
# Format: 'TYPE,HOST,PORT' (comma seperated, no spaces!)
# add as many lines as needed using += as shown
#
# TYPE is one of: ipv4, ipv6
# HOST is the remote host to look up in DNS
# PORT is the local incoming port to open
#
# Examples:
#DHOSTS+=('ipv4,host1.domain.com,10051')
#DHOSTS+=('ipv6,host1.domain.com,10051')
#DHOSTS+=('ipv4,host2.domain.com,10051')
#DHOSTS+=('ipv6,host3.domain.com,10051')
# Name of dyname chain for both IPv4 and IPv6 - IT WILL BE FLUSHED
#
# The named chain must be created in your iptables chains FIRST manually
# - this script does not create/delete the basic (empty) chain!
# - example:
#
# :DYNAMIC - [0:0]
# -A INPUT -j DYNAMIC
#
# - you must use a unique name (not INPUT, etc.)
# - iptables has a 30char limit on chain names
#
DCHAIN="DYNAMIC"

4
dyniptables.ipt Normal file
View file

@ -0,0 +1,4 @@
# example chain for iptables files
:DYNAMIC - [0:0]
-A INPUT -j DYNAMIC

347
dyniptables.sh Executable file
View file

@ -0,0 +1,347 @@
#!/usr/bin/env bash
#
# Rebuild dynamic IPtables chain with DNS lookups of named hosts
# Required utils: getent, grep, cut, iptables, logger, stat
#
# SPDX-License-Identifier: MIT
_VERSION="0.0.4"
_NAME=$(basename "${0}")
# Server prep and usage:
# 1. Place script in /usr/local/sbin/dyniptables.sh (root:root, 0744)
#
# 2. Add to system on-boot iptables rules a new filter chain and (j)ump:
# :DYNAMIC - [0:0]
# -A INPUT -j DYNAMIC
# ...where DYNAMIC is the name of the $DCHAIN below
#
# 3. Add to root's crontab a refresh every 6 hours:
# 5 */6 * * * /usr/local/sbin/dyniptables.sh
#
# 4. Add an override.conf to systemd iptables startup:
# DEB clones: `systemctl edit netfilter-persistent.service`
# or
# RPM clones: `systemctl edit iptables.service` (IPv4)
# `systemctl edit ip6tables.service` (IPv6)
#
# [Service]
# ExecStartPost=/usr/local/sbin/dyniptables.sh # (DEB, all rules)
# or
# ExecStartPost=/usr/local/sbin/dyniptables.sh -4 # (RPM, IPv4 only)
# ExecStartPost=/usr/local/sbin/dyniptables.sh -6 # (RPM, IPv6 only)
# List of hosts, declare emtpy array (do not edit)
declare -a DHOSTS
# Name of chain, declare empty variable
declare DCHAIN=""
# Our config for DHOSTS and DCHAIN
_CONFIG="/etc/dyniptables.conf"
##############################################################################
## Regular Expressions to match IPv4/IPv6
## https://stackoverflow.com/a/17871737/150649
## https://gist.github.com/syzdek/6086792
RE_IPV4="((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.)"
RE_IPV4="${RE_IPV4}{3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"
# [1:2:3:4:5:6:7:8]
RE_IPV6="([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|"
# [1::] 1:2:3:4:5:6:7::
RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,7}:|"
# [1::8] 1:2:3:4:5:6::8 1:2:3:4:5:6::8
RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|"
# [1::7:8] 1:2:3:4:5::7:8 1:2:3:4:5::8
RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|"
# [1::6:7:8] 1:2:3:4::6:7:8 1:2:3:4::8
RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|"
# [1::5:6:7:8] 1:2:3::5:6:7:8 1:2:3::8
RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|"
# [1::4:5:6:7:8] 1:2::4:5:6:7:8 1:2::8
RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|"
# [1::3:4:5:6:7:8] 1::3:4:5:6:7:8 1::8
RE_IPV6="${RE_IPV6}[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|"
# [::2:3:4:5:6:7:8] ::2:3:4:5:6:7:8 ::8 ::
RE_IPV6="${RE_IPV6}:((:[0-9a-fA-F]{1,4}){1,7}|:)|"
# [fe08::7:8%eth0] fe08::7:8%1
RE_IPV6="${RE_IPV6}fe08:(:[0-9a-fA-F]{1,4}){2,2}%[0-9a-zA-Z]{1,}|"
# [::255.255.255.255] ::ffff:255.255.255.255 ::ffff:0:255.255.255.255
RE_IPV6="${RE_IPV6}::(ffff(:0{1,4}){0,1}:){0,1}${RE_IPV4}|"
# [2001:db8:3:4::192.0.2.33] 64:ff9b::192.0.2.33
RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,4}:${RE_IPV4}"
##
##############################################################################
# Internal variables
declare _QUIET=0 _RULESV4=1 _RULESV6=1 _VALID4=1 _VALID6=1
# Allow Ctrl+C, etc.
function error_exit() {
echo "Trapped a kill signal, exiting."
exit 99
}
trap error_exit SIGHUP SIGINT SIGTERM
# Log to SYSLOG
function logme() {
if [[ $_QUIET -eq 0 ]]; then
echo "$1" | logger -t dyniptables
fi
}
# Converting input to int for port check
function to_int() {
local -i _num="10#${1}"
echo "${_num}"
}
# Config
function create_config() {
if [[ -f "${_CONFIG}" ]]; then
echo "Config file ${_CONFIG} already exists, not creating."
return 1
fi
echo "Creating config ${_CONFIG}"
cat << 'EOCONF' >> "${_CONFIG}"
# dyniptables config file
# permissions on this file must be root:root, 0644
# The DHOSTS array is already declared; uncomment and add a line for
# each host to allow, notice '+=' to ADD to the array each line.
#
# Format: 'TYPE,HOST,PORT' (comma seperated, no spaces!)
# add as many lines as needed using += as shown
#
# TYPE is one of: ipv4, ipv6
# HOST is the remote host to look up in DNS
# PORT is the local incoming port to open
#
# Examples:
#DHOSTS+=('ipv4,host1.domain.com,10051')
#DHOSTS+=('ipv6,host1.domain.com,10051')
#DHOSTS+=('ipv4,host2.domain.com,10051')
#DHOSTS+=('ipv6,host3.domain.com,10051')
# Name of dyname chain for both IPv4 and IPv6 - IT WILL BE FLUSHED
#
# The named chain must be created in your iptables chains FIRST manually
# - this script does not create/delete the basic (empty) chain!
# - example:
#
# :DYNAMIC - [0:0]
# -A INPUT -j DYNAMIC
#
# - you must use a unique name (not INPUT, etc.)
# - iptables has a 30char limit on chain names
#
DCHAIN="DYNAMIC"
EOCONF
# check our work
if [[ -f "${_CONFIG}" ]]; then
chown root:root "${_CONFIG}"
chmod 0644 "${_CONFIG}"
if [[ $(stat -c "%u:%g:%a" "${_CONFIG}") != "0:0:644" ]]; then
echo "ERROR: incorrect permissions on ${_CONFIG}"
return 1
fi
else
echo "ERROR: failed to create ${_CONFIG}"
return 1
fi
return 0
}
# Usage
function usage() {
echo "$_NAME version $_VERSION"
echo
echo "Rebuild dynamic IP chain with DNS lookups of named hosts"
echo
echo " Usage: $_NAME [-q] [-4|-6] || [-C|-V|-h]"
echo " Default behavior: both IPv4 and IPv6 rules processed."
echo
echo " -C Create default config and exit"
echo " -4 Process IPv4 rules only"
echo " -6 Process IPv6 rules only"
echo " -q Suppress logging (quiet mode)"
echo " -V Version of program"
echo " -h This help text"
echo
echo " Note: -4 and -6 together results in no rules processed."
}
# Commandline options
while getopts ":C46qVh" opt; do
case "$opt" in
C)
create_config
exit $? ;;
4)
_RULESV6=0 ;;
6)
_RULESV4=0 ;;
q)
_QUIET=1 ;;
V)
echo "$_NAME version $_VERSION"
exit 0 ;;
h)
usage
exit 0 ;;
*)
echo "Unrecognized option: $OPTARG (Run '$_NAME -h' for help)"
exit 1 ;;
esac
done
shift $((OPTIND-1))
# Attempt to read in our config
if [[ -r "${_CONFIG}" ]]; then
if [[ $(stat -c "%u:%g:%a" "${_CONFIG}") != "0:0:644" ]]; then
echo "ERROR: Config file security - ${_CONFIG}"
echo "ERROR: Config is not 'chown root:root', 'chmod 644' - exiting."
exit 1
else
# shellcheck source=/dev/null
source "${_CONFIG}"
if [[ ${#DHOSTS[@]} -eq 0 ]]; then
echo "ERROR: no hosts configred in DHOSTS, exiting."
exit 1
fi
if [[ -z "${DCHAIN}" ]]; then
echo "ERROR: no chain configured in DCHAIN, exiting."
exit 1
fi
fi
else
echo "ERROR: Missing config ${_CONFIG}"
echo "ERROR: Run '$_NAME -C' to create default, then edit."
exit 1
fi
# Networking may not be up; running a bad iptable rule can cause the
# service to fail the ExecStartPost in systemd and leave the system
# open. A lot of error checking is required for safety.
# Flush the existing chains; if the IP changed, we don't know what the
# old host->IP was without maintaining our own database
if [[ $_RULESV4 -eq 1 ]]; then
logme "Flushing $DCHAIN IPv4 chain"
/sbin/iptables -F "${DCHAIN}"
# shellcheck disable=SC2181
if [[ $? -ne 0 ]]; then
logme "IPv4 chain ${DCHAIN} flush error, skipping IPv4 rules"
_VALID4=0
fi
fi
if [[ $_RULESV6 -eq 1 ]]; then
logme "Flushing $DCHAIN IPv6 chain"
/sbin/ip6tables -F "${DCHAIN}"
# shellcheck disable=SC2181
if [[ $? -ne 0 ]]; then
logme "IPv6 chain ${DCHAIN} flush error, skipping IPv6 rules"
_VALID6=0
fi
fi
# Loop through the entries, take action
# for each element:
# convert comma to space to make new child array
# _tmparray[0] = TYPE
# _tmparray[1] = HOST
# _tmparray[2] = PORT
# shellcheck disable=SC2068
for iprule in ${DHOSTS[@]}; do
# shellcheck disable=SC2206
_tmparray=(${iprule//,/ })
# ensure 3 elements in the array
if [[ ${#_tmparray[@]} -ne 3 ]]; then
logme "Malformed entry, skipping: $iprule"
continue
fi
# port check
# https://docwhat.org/bash-checking-a-port-number
_portnum=$(to_int "${_tmparray[2]}" 2>/dev/null)
# shellcheck disable=SC2004
if (( $_portnum < 1 || $_portnum > 65535 )) ; then
logme "Invalid port, skipping: $iprule"
continue
fi
# IPv4 host
if [[ "${_tmparray[0]}" == "ipv4" ]]; then
# Silently skip if IPv4 ruls are disabled
if [[ $_RULESV4 -ne 1 ]]; then
continue
fi
# If we couldn't flush the chain, skip the rule
if [[ $_VALID4 -ne 1 ]]; then
logme "IPv4 chain error, skipping: $iprule"
continue
fi
# networking may not be up
_hostip=$(getent ahostsv4 "${_tmparray[1]}" | \
grep STREAM | \
cut -d' ' -f1 | \
grep -iE "${RE_IPV4}")
if [[ -z "$_hostip" ]]; then
logme "Unknown host, skipping: $iprule"
continue
fi
# add the rule if all checks pass
logme "Adding $_hostip to ${_tmparray[0]} chain, port ${_tmparray[2]}"
/sbin/iptables -I "$DCHAIN" -p tcp -m tcp \
-s i"${_hostip}" --dport "${_tmparray[2]}" -j ACCEPT
# IPv6 host
elif [[ "${_tmparray[0]}" == "ipv6" ]]; then
# Silently skip if IPv6 ruls are disabled
if [[ $_RULESV6 -ne 1 ]]; then
continue
fi
# If we couldn't flush the chain, skip the rule
if [[ $_VALID6 -ne 1 ]]; then
logme "IPv6 chain error, skipping: $iprule"
continue
fi
# networking may not be up
_hostip=$(getent ahostsv6 "${_tmparray[1]}" | \
grep STREAM | \
cut -d' ' -f1 | \
grep -iE "${RE_IPV6}")
if [[ -z "$_hostip" ]]; then
logme "Unknown host, skipping: $iprule"
continue
fi
# add the rule if all checks pass
logme "Adding $_hostip to ${_tmparray[0]} chain, port ${_tmparray[2]}"
/sbin/ip6tables -I "$DCHAIN" -p tcp -m tcp \
-s "${_hostip}" --dport "${_tmparray[2]}" -j ACCEPT
# Oops
else
logme "Invalid type, skipping: $iprule"
continue
fi
done;
# Clean exit
exit 0