importing new rebuild
This commit is contained in:
parent
a6ebf87180
commit
9e832a0fe3
4 changed files with 411 additions and 0 deletions
28
README.md
28
README.md
|
|
@ -2,4 +2,32 @@
|
|||
|
||||
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
|
||||
|
||||
|
|
|
|||
32
dyniptables.conf
Normal file
32
dyniptables.conf
Normal 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
4
dyniptables.ipt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# example chain for iptables files
|
||||
|
||||
:DYNAMIC - [0:0]
|
||||
-A INPUT -j DYNAMIC
|
||||
347
dyniptables.sh
Executable file
347
dyniptables.sh
Executable 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue