-
Notifications
You must be signed in to change notification settings - Fork 0
/
posix-nftables-apply
204 lines (170 loc) · 5.69 KB
/
posix-nftables-apply
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
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
203
204
#!/usr/bin/env sh
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version. Please see LICENSE.txt at the top level of
# the source code distribution for details.
#
# @package nftables-apply
# @author <[email protected]>
# @link https://github.com/fbouynot/scripts/blob/main/posix-nftables-apply
# @copyright <[email protected]>
#
# Free adaptation for nftables of iptables-apply (https://github.com/wertarbyte/iptables/blob/master/iptables-apply)
#
# -e: When a command fails, bash exits instead of continuing with the rest of the script
# -u: This will make the script fail, when accessing an unset variable
set -eu
# Trap to remove TMPFILE if the script is stopped before the cleanup
trap 'rm -f "${TMPFILE}"' EXIT
# Replace the Internal Field Separator ' \n\t' by '\n\t' so you can loop through names with spaces
IFS=$(printf '\n\t')
# Enable debug mode by running your script as TRACE=1 ./script.sh instead of ./script.sh
if [ "${TRACE-0}" = "1" ]
then
set -o xtrace
fi
# Define constants
PROGNAME="${0##*/}"
VERSION='1.3.0'
RED="$(tput setaf 1)" || exit 1
NC="$(tput sgr0)" || exit 1 # No Color
TMPFILE=$(mktemp) || exit 1
DEFAULT_TIMEOUT=15
DEFAULT_DESTINATION_FILE='/etc/nftables.conf'
DEFAULT_SOURCE_FILE='/etc/nftables-candidate.conf'
readonly PROGNAME VERSION RED NC DEFAULT_TIMEOUT DEFAULT_DESTINATION_FILE DEFAULT_SOURCE_FILE
help() {
cat << EOF
Usage: ${PROGNAME} [-Vh] [ { -s | --source-file } <source-file> ] [ { -d | --destination-file } <destination-file> ] [ { -t | --timeout } <timeout> ]
-h --help Print this message.
-V --version Print the version.
-s --source-file STRING The source file for candidate config. (default: ${DEFAULT_SOURCE_FILE})
-d --destination-file STRING The destination file where to write the config. (default: ${DEFAULT_DESTINATION_FILE})
-t --timeout INT The time to wait before rolling back. (default: ${DEFAULT_TIMEOUT})
EOF
exit 2
}
version() {
cat << EOF
${PROGNAME} version ${VERSION} under GPLv3 licence.
EOF
exit 2
}
# Deal with arguments
while [ $# -gt 0 ]
do
key="${1}"
case $key in
-h|--help)
help
;;
-s|--source-file)
export source_file="${2}"
shift # consume -s
;;
-d|--destination-file)
export destination_file="${2}"
shift # consume -d
;;
-t|--timeout)
export timeout="${2}"
shift # consume -t
;;
-V|--version)
version
;;
*)
shift
;;
esac
shift # consume $1
done
# Set defaults if no options specified
source_file="${source_file:-$DEFAULT_SOURCE_FILE}"
destination_file="${destination_file:-$DEFAULT_DESTINATION_FILE}"
timeout="${timeout:-$DEFAULT_TIMEOUT}"
# Change directory to base script directory
cd "$(dirname "${0}")"
# Check root permissions
check_root() {
# Check the command is run as root
if [ "$(id -u)" -ne 0 ]
then
printf "%sE:%s please run as root\n" "${RED}" "${NC}" >&2
exit 3
fi
return 0
}
restore() {
nft flush ruleset
nft -f "${TMPFILE}"
# Start fail2ban
if systemctl is-enabled fail2ban > /dev/null 2>&1
then
systemctl start fail2ban 2>/dev/null
fi
return 0
}
save() {
cp "${source_file}" "${destination_file}"
printf "\nConfiguration changed\n"
return 0
}
# Main function
main() {
# Check the command is run as root
check_root
# Check if we can read the destination file
if [ ! -r "${destination_file}" ]
then
printf "%sE:%s cannot read %s\n" "${RED}" "${NC}" "${destination_file}" >&2
exit 4
fi
# Backup current ruleset
nft list ruleset > "${TMPFILE}"
# Check if we can read the source file
if [ ! -r "${source_file}" ]
then
printf "%sE:%s cannot read %s\n" "${RED}" "${NC}" "${source_file}" >&2
exit 5
fi
# Dry run new ruleset, exit if failures
nft -f "${source_file}" || (printf "%sE:%s Invalid rules, exiting\n" "${RED}" "${NC}" >&2 && exit 6)
# Check the candidate configuration starts by flushing ruleset
if [ "$(head -n 1 /etc/nftables-candidate.conf)" != "flush ruleset" ]
then
sed -i '1s/^/flush ruleset\n/' "${source_file}"
fi
# Stop fail2ban
if systemctl is-active fail2ban > /dev/null 2>&1
then
systemctl stop fail2ban 2>/dev/null
fi
# Apply new ruleset, rollback if timeout
timeout "${timeout}"s nft -f "${source_file}" || (printf "%sE:%s timeout while applying new configuration, rolling back to the previous ruleset\n" "${RED}" "${NC}" >&2 && restore && exit 7)
# Ask the user if they can open a new connection
# If they can't, rollback
# If they can, save
printf "Can you establish NEW connections to the machine? (y/N) "
stty -icanon -echo
timeout "${timeout}" answer="$(dd bs=1 count=1 2> /dev/null)" 2>&1 || :
if [ "${answer:?}" = "y" ]
then
stty icanon echo
save
else
stty icanon echo
printf "%sE:%s rolling back to the previous ruleset\n" "${RED}" "${NC}" >&2
restore
exit 8
fi
# Start fail2ban
if systemctl is-enabled fail2ban > /dev/null 2>&1
then
systemctl start fail2ban 2>/dev/null
fi
exit 0
}
main "$@"