296 lines
6.9 KiB
Bash
Executable File
296 lines
6.9 KiB
Bash
Executable File
#!/bin/bash
|
|
# iptables-apply -- a safer way to update iptables remotely
|
|
#
|
|
# Usage:
|
|
# iptables-apply [-hV] [-t timeout] [-w savefile] {[rulesfile]|-c [runcmd]}
|
|
#
|
|
# Versions:
|
|
# * 1.0 Copyright 2006 Martin F. Krafft <madduck@madduck.net>
|
|
# Original version
|
|
# * 1.1 Copyright 2010 GW <gw.2010@tnode.com or http://gw.tnode.com/>
|
|
# Added parameter -c (run command)
|
|
# Added parameter -w (save successfully applied rules to file)
|
|
# Major code cleanup
|
|
#
|
|
# Released under the terms of the Artistic Licence 2.0
|
|
#
|
|
set -eu
|
|
|
|
PROGNAME="${0##*/}"
|
|
VERSION=1.1
|
|
|
|
|
|
### Default settings
|
|
|
|
DEF_TIMEOUT=10
|
|
|
|
MODE=0 # apply rulesfile mode
|
|
# MODE=1 # run command mode
|
|
|
|
case "$PROGNAME" in
|
|
(*6*)
|
|
SAVE=ip6tables-save
|
|
RESTORE=ip6tables-restore
|
|
DEF_RULESFILE="/etc/network/ip6tables.up.rules"
|
|
DEF_SAVEFILE="$DEF_RULESFILE"
|
|
DEF_RUNCMD="/etc/network/ip6tables.up.run"
|
|
;;
|
|
(*)
|
|
SAVE=iptables-save
|
|
RESTORE=iptables-restore
|
|
DEF_RULESFILE="/etc/network/iptables.up.rules"
|
|
DEF_SAVEFILE="$DEF_RULESFILE"
|
|
DEF_RUNCMD="/etc/network/iptables.up.run"
|
|
;;
|
|
esac
|
|
|
|
|
|
### Functions
|
|
|
|
function blurb() {
|
|
cat <<-__EOF__
|
|
$PROGNAME $VERSION -- a safer way to update iptables remotely
|
|
__EOF__
|
|
}
|
|
|
|
function copyright() {
|
|
cat <<-__EOF__
|
|
$PROGNAME has been published under the terms of the Artistic Licence 2.0.
|
|
|
|
Original version - Copyright 2006 Martin F. Krafft <madduck@madduck.net>.
|
|
Version 1.1 - Copyright 2010 GW <gw.2010@tnode.com or http://gw.tnode.com/>.
|
|
__EOF__
|
|
}
|
|
|
|
function about() {
|
|
blurb
|
|
echo
|
|
copyright
|
|
}
|
|
|
|
function usage() {
|
|
blurb
|
|
echo
|
|
cat <<-__EOF__
|
|
Usage:
|
|
$PROGNAME [-hV] [-t timeout] [-w savefile] {[rulesfile]|-c [runcmd]}
|
|
|
|
The script will try to apply a new rulesfile (as output by iptables-save,
|
|
read by iptables-restore) or run a command to configure iptables and then
|
|
prompt the user whether the changes are okay. If the new iptables rules cut
|
|
the existing connection, the user will not be able to answer affirmatively.
|
|
In this case, the script rolls back to the previous working iptables rules
|
|
after the timeout expires.
|
|
|
|
Successfully applied rules can also be written to savefile and later used
|
|
to roll back to this state. This can be used to implement a store last good
|
|
configuration mechanism when experimenting with an iptables setup script:
|
|
$PROGNAME -w $DEF_SAVEFILE -c $DEF_RUNCMD
|
|
|
|
When called as ip6tables-apply, the script will use ip6tables-save/-restore
|
|
and IPv6 default values instead. Default value for rulesfile is
|
|
'$DEF_RULESFILE'.
|
|
|
|
Options:
|
|
|
|
-t seconds, --timeout seconds
|
|
Specify the timeout in seconds (default: $DEF_TIMEOUT).
|
|
-w savefile, --write savefile
|
|
Specify the savefile where successfully applied rules will be written to
|
|
(default if empty string is given: $DEF_SAVEFILE).
|
|
-c runcmd, --command runcmd
|
|
Run command runcmd to configure iptables instead of applying a rulesfile
|
|
(default: $DEF_RUNCMD).
|
|
-h, --help
|
|
Display this help text.
|
|
-V, --version
|
|
Display version information.
|
|
|
|
__EOF__
|
|
}
|
|
|
|
function checkcommands() {
|
|
for cmd in "${COMMANDS[@]}"; do
|
|
if ! command -v "$cmd" >/dev/null; then
|
|
echo "Error: needed command not found: $cmd" >&2
|
|
exit 127
|
|
fi
|
|
done
|
|
}
|
|
|
|
function revertrules() {
|
|
echo -n "Reverting to old iptables rules... "
|
|
"$RESTORE" <"$TMPFILE"
|
|
echo "done."
|
|
}
|
|
|
|
|
|
### Parsing and checking parameters
|
|
|
|
TIMEOUT="$DEF_TIMEOUT"
|
|
SAVEFILE=""
|
|
|
|
SHORTOPTS="t:w:chV";
|
|
LONGOPTS="timeout:,write:,command,help,version";
|
|
|
|
OPTS=$(getopt -s bash -o "$SHORTOPTS" -l "$LONGOPTS" -n "$PROGNAME" -- "$@") || exit $?
|
|
for opt in $OPTS; do
|
|
case "$opt" in
|
|
(-*)
|
|
unset OPT_STATE
|
|
;;
|
|
(*)
|
|
case "${OPT_STATE:-}" in
|
|
(SET_TIMEOUT) eval TIMEOUT=$opt;;
|
|
(SET_SAVEFILE)
|
|
eval SAVEFILE=$opt
|
|
[ -z "$SAVEFILE" ] && SAVEFILE="$DEF_SAVEFILE"
|
|
;;
|
|
esac
|
|
;;
|
|
esac
|
|
|
|
case "$opt" in
|
|
(-t|--timeout) OPT_STATE="SET_TIMEOUT";;
|
|
(-w|--write) OPT_STATE="SET_SAVEFILE";;
|
|
(-c|--command) MODE=1;;
|
|
(-h|--help) usage >&2; exit 0;;
|
|
(-V|--version) about >&2; exit 0;;
|
|
(--) break;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
# Validate parameters
|
|
if [ "$TIMEOUT" -ge 0 ] 2>/dev/null; then
|
|
TIMEOUT=$(($TIMEOUT))
|
|
else
|
|
echo "Error: timeout must be a positive number" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ -n "$SAVEFILE" -a -e "$SAVEFILE" -a ! -w "$SAVEFILE" ]; then
|
|
echo "Error: savefile not writable: $SAVEFILE" >&2
|
|
exit 8
|
|
fi
|
|
|
|
case "$MODE" in
|
|
(1)
|
|
# Treat parameter as runcmd (run command mode)
|
|
RUNCMD="${1:-$DEF_RUNCMD}"
|
|
if [ ! -x "$RUNCMD" ]; then
|
|
echo "Error: runcmd not executable: $RUNCMD" >&2
|
|
exit 6
|
|
fi
|
|
|
|
# Needed commands
|
|
COMMANDS=(mktemp "$SAVE" "$RESTORE" "$RUNCMD")
|
|
checkcommands
|
|
;;
|
|
(*)
|
|
# Treat parameter as rulesfile (apply rulesfile mode)
|
|
RULESFILE="${1:-$DEF_RULESFILE}";
|
|
if [ ! -r "$RULESFILE" ]; then
|
|
echo "Error: rulesfile not readable: $RULESFILE" >&2
|
|
exit 2
|
|
fi
|
|
|
|
# Needed commands
|
|
COMMANDS=(mktemp "$SAVE" "$RESTORE")
|
|
checkcommands
|
|
;;
|
|
esac
|
|
|
|
|
|
### Begin work
|
|
|
|
# Store old iptables rules to temporary file
|
|
TMPFILE=`mktemp /tmp/$PROGNAME-XXXXXXXX`
|
|
trap "rm -f $TMPFILE" EXIT 1 2 3 4 5 6 7 8 10 11 12 13 14 15
|
|
|
|
if ! "$SAVE" >"$TMPFILE"; then
|
|
# An error occured
|
|
if ! grep -q ipt /proc/modules 2>/dev/null; then
|
|
echo "Error: iptables support lacking from the kernel" >&2
|
|
exit 3
|
|
else
|
|
echo "Error: unknown error saving old iptables rules: $TMPFILE" >&2
|
|
exit 4
|
|
fi
|
|
fi
|
|
|
|
# Legacy to stop the fail2ban daemon if present
|
|
[ -x /etc/init.d/fail2ban ] && /etc/init.d/fail2ban stop
|
|
|
|
# Configure iptables
|
|
case "$MODE" in
|
|
(1)
|
|
# Run command in background and kill it if it times out
|
|
echo -n "Running command '$RUNCMD'... "
|
|
"$RUNCMD" &
|
|
CMD_PID=$!
|
|
( sleep "$TIMEOUT"; kill "$CMD_PID" 2>/dev/null; exit 0 ) &
|
|
CMDTIMEOUT_PID=$!
|
|
if ! wait "$CMD_PID"; then
|
|
echo "failed."
|
|
echo "Error: unknown error running command: $RUNCMD" >&2
|
|
revertrules
|
|
exit 7
|
|
else
|
|
echo "done."
|
|
fi
|
|
;;
|
|
(*)
|
|
# Apply iptables rulesfile
|
|
echo -n "Applying new iptables rules from '$RULESFILE'... "
|
|
if ! "$RESTORE" <"$RULESFILE"; then
|
|
echo "failed."
|
|
echo "Error: unknown error applying new iptables rules: $RULESFILE" >&2
|
|
revertrules
|
|
exit 5
|
|
else
|
|
echo "done."
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
# Prompt user for confirmation
|
|
echo -n "Can you establish NEW connections to the machine? (y/N) "
|
|
|
|
read -n1 -t "$TIMEOUT" ret 2>&1 || :
|
|
case "${ret:-}" in
|
|
(y*|Y*)
|
|
# Success
|
|
echo
|
|
|
|
if [ ! -z "$SAVEFILE" ]; then
|
|
# Write successfully applied rules to the savefile
|
|
echo "Writing successfully applied rules to '$SAVEFILE'..."
|
|
if ! "$SAVE" >"$SAVEFILE"; then
|
|
echo "Error: unknown error writing successfully applied rules: $SAVEFILE" >&2
|
|
exit 9
|
|
fi
|
|
fi
|
|
|
|
echo "... then my job is done. See you next time."
|
|
;;
|
|
(*)
|
|
# Failed
|
|
echo
|
|
if [ -z "${ret:-}" ]; then
|
|
echo "Timeout! Something happened (or did not). Better play it safe..."
|
|
else
|
|
echo "No affirmative response! Better play it safe..."
|
|
fi
|
|
revertrules
|
|
exit 255
|
|
;;
|
|
esac
|
|
|
|
# Legacy to start the fail2ban daemon again
|
|
[ -x /etc/init.d/fail2ban ] && /etc/init.d/fail2ban start
|
|
|
|
exit 0
|
|
|
|
# vim:noet:sw=8
|