iptables-apply 6.89 KB
Newer Older
1
2
3
#!/bin/bash
# iptables-apply -- a safer way to update iptables remotely
#
4
5
6
7
8
9
10
11
12
13
14
# 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
#
15
16
17
18
# Released under the terms of the Artistic Licence 2.0
#
set -eu

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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

47

48
### Functions
49

50
51
function blurb() {
	cat <<-__EOF__
52
	$PROGNAME $VERSION -- a safer way to update iptables remotely
53
	__EOF__
54
55
}

56
57
58
function copyright() {
	cat <<-__EOF__
	$PROGNAME has been published under the terms of the Artistic Licence 2.0.
59

60
61
62
	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__
63
64
}

65
function about() {
66
67
68
69
70
	blurb
	echo
	copyright
}

71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
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
89

90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
	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__
}
111

112
113
114
115
116
117
118
119
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
}
120

121
122
123
124
function revertrules() {
	echo -n "Reverting to old iptables rules... "
	"$RESTORE" <"$TMPFILE"
	echo "done."
125
126
}

127
128
129
130
131
132
133
134

### Parsing and checking parameters

TIMEOUT="$DEF_TIMEOUT"
SAVEFILE=""

SHORTOPTS="t:w:chV";
LONGOPTS="timeout:,write:,command,help,version";
135
136
137
138

OPTS=$(getopt -s bash -o "$SHORTOPTS" -l "$LONGOPTS" -n "$PROGNAME" -- "$@") || exit $?
for opt in $OPTS; do
	case "$opt" in
139
140
141
		(-*)
			unset OPT_STATE
			;;
142
143
		(*)
			case "${OPT_STATE:-}" in
144
145
146
147
				(SET_TIMEOUT) eval TIMEOUT=$opt;;
				(SET_SAVEFILE)
					eval SAVEFILE=$opt
					[ -z "$SAVEFILE" ] && SAVEFILE="$DEF_SAVEFILE"
148
149
150
151
152
153
					;;
			esac
			;;
	esac

	case "$opt" in
154
155
156
		(-t|--timeout) OPT_STATE="SET_TIMEOUT";;
		(-w|--write) OPT_STATE="SET_SAVEFILE";;
		(-c|--command) MODE=1;;
157
158
159
160
161
162
163
		(-h|--help) usage >&2; exit 0;;
		(-V|--version) about >&2; exit 0;;
		(--) break;;
	esac
	shift
done

164
165
166
167
168
# Validate parameters
if [ "$TIMEOUT" -ge 0 ] 2>/dev/null; then
	TIMEOUT=$(($TIMEOUT))
else
	echo "Error: timeout must be a positive number" >&2
169
170
171
	exit 1
fi

172
173
174
if [ -n "$SAVEFILE" -a -e "$SAVEFILE" -a ! -w "$SAVEFILE" ]; then
	echo "Error: savefile not writable: $SAVEFILE" >&2
	exit 8
175
176
fi

177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
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
203
204


205
### Begin work
206

207
208
# Store old iptables rules to temporary file
TMPFILE=`mktemp /tmp/$PROGNAME-XXXXXXXX`
209
210
trap "rm -f $TMPFILE" EXIT HUP INT QUIT ILL TRAP ABRT BUS \
		      FPE USR1 SEGV USR2 PIPE ALRM TERM
211
212

if ! "$SAVE" >"$TMPFILE"; then
213
	# An error occured
214
	if ! grep -q ipt /proc/modules 2>/dev/null; then
215
		echo "Error: iptables support lacking from the kernel" >&2
216
217
		exit 3
	else
218
		echo "Error: unknown error saving old iptables rules: $TMPFILE" >&2
219
220
221
222
		exit 4
	fi
fi

223
# Legacy to stop the fail2ban daemon if present
224
225
[ -x /etc/init.d/fail2ban ] && /etc/init.d/fail2ban stop

226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# 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
257

258
# Prompt user for confirmation
259
260
echo -n "Can you establish NEW connections to the machine? (y/N) "

261
read -n1 -t "$TIMEOUT" ret 2>&1 || :
262
263
case "${ret:-}" in
	(y*|Y*)
264
		# Success
265
		echo
266
267
268
269
270
271
272
273
274
275

		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

276
		echo "... then my job is done. See you next time."
277
278
		;;
	(*)
279
280
281
282
		# Failed
		echo
		if [ -z "${ret:-}" ]; then
			echo "Timeout! Something happened (or did not). Better play it safe..."
283
		else
284
			echo "No affirmative response! Better play it safe..."
285
		fi
286
		revertrules
287
288
289
290
		exit 255
		;;
esac

291
# Legacy to start the fail2ban daemon again
292
293
294
295
296
[ -x /etc/init.d/fail2ban ] && /etc/init.d/fail2ban start

exit 0

# vim:noet:sw=8