245 lines
5.3 KiB
Bash
Executable File
245 lines
5.3 KiB
Bash
Executable File
#!/bin/bash
|
|
# Summary: Interactively edit the current nftables ruleset.
|
|
# Author: Tilman Kranz
|
|
# Authoremail: tilt@linuxfoo.de
|
|
# License: MIT License (https://opensource.org/licenses/MIT)
|
|
|
|
##
|
|
# Configuration
|
|
|
|
if test -w "/etc/nftables.conf" ; then
|
|
default_config_file="/etc/nftables.conf"
|
|
elif test -w "/etc/sysconfig/nftables.conf" ; then
|
|
default_config_file="/etc/sysconfig/nftables.conf"
|
|
else
|
|
default_config_file=""
|
|
fi
|
|
|
|
##
|
|
# Functions
|
|
|
|
cleanup() {
|
|
! "$timeout" && test -n "$tmp_old" && rm -f "$tmp_old"
|
|
test -n "$tmp" && rm -f "$tmp"
|
|
}
|
|
|
|
at_int() {
|
|
cleanup
|
|
exit 1
|
|
}
|
|
|
|
at_exit() {
|
|
cleanup
|
|
}
|
|
|
|
set_timeout() {
|
|
local ruleset=$1
|
|
local seconds=$2
|
|
|
|
systemd-run \
|
|
--description="Reverting nftables ruleset changes by $0 (PID=$$)" \
|
|
--on-active="$seconds" \
|
|
--timer-property=AccuracySec=100ms \
|
|
--quiet \
|
|
bash -c "nft -f '$ruleset' ; rm -f '$ruleset'"
|
|
}
|
|
|
|
store_config() {
|
|
local ruleset=$1
|
|
local conf=$2
|
|
|
|
if ! printf "#!/usr/sbin/nft -f\n# Generated at %s by $0\n\n" "$(date -R)" > "$conf" ; then
|
|
return 1
|
|
elif ! cat "$ruleset" >> "$conf" ; then
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
##
|
|
# Arguments
|
|
|
|
config=false
|
|
fail=false
|
|
timeout=false
|
|
yes=false
|
|
|
|
while true ; do
|
|
case "$1" in
|
|
-h|--help)
|
|
# shellcheck disable=SC2016
|
|
backticks='```'
|
|
cat << EOF
|
|
# SYNOPSIS
|
|
$backticks
|
|
nft-edit-ruleset [--config [CONFIG_FILE]] [--fail] [--timeout SECONDS] [--yes]
|
|
$backticks
|
|
|
|
# DESCRIPTION
|
|
Interactively edit the current nftables ruleset using the editor specified by
|
|
environment variable EDITOR (defaulting to vim). Optionally, revert changes
|
|
after a timeout given in seconds.
|
|
|
|
# OPTIONS
|
|
- \`-c\`, \`--config [CONFIG_FILE]\`:
|
|
On success, save ruleset to \`CONFIG_FILE\` (default: $default_config_file).
|
|
- \`-f\`, \`--fail\`:
|
|
Exit unsuccessfully if changes fail to apply.
|
|
- \`-h\`, \`--help\`:
|
|
Display this message and exit.
|
|
- \`-t NUM\`, \`--timeout NUM\`:
|
|
Revert changes after NUM seconds.
|
|
- \`-y\`, \`--yes\`:
|
|
No confirmation before applying changes.
|
|
|
|
# EXIT CODES
|
|
- 0: Success: No changes to apply or changes applied successfully.
|
|
- 1: Error: Usage error or failed or aborted changes.
|
|
|
|
# AUTHOR AND LICENSE
|
|
Copyleft 2021 Tilman Kranz <tilt@linuxfoo.de>
|
|
This software is distributed on the terms and conditions of the
|
|
MIT License [https://opensource.org/licenses/MIT](https://opensource.org/licenses/MIT)
|
|
EOF
|
|
exit 0
|
|
;;
|
|
-f|--fail)
|
|
fail=true
|
|
;;
|
|
-y|--yes)
|
|
yes=true
|
|
;;
|
|
-t|--timeout)
|
|
if test "$#" -gt 0 ; then
|
|
shift 1
|
|
timeout=true
|
|
timeout_secs=$1
|
|
else
|
|
echo "ERROR: Missing argument for option \`--timeout\`, aborted." >&2
|
|
exit 1
|
|
fi
|
|
;;
|
|
-c|--config)
|
|
config=true
|
|
|
|
if test "$#" -gt 0 && { test -w "$2" || test -w "$(dirname "$2")" ; } ; then
|
|
shift 1
|
|
config_file=$1
|
|
elif test -z "$default_config_file" ; then
|
|
echo \
|
|
"ERROR: Option \`--config\` was used without an argument," \
|
|
"but no default location of a file \"nftables.conf\" could be found" \
|
|
"(use \`--config CONFIG_FILE\` to specify a location); aborted." >&2
|
|
exit 1
|
|
else
|
|
config_file=$default_config_file
|
|
fi
|
|
;;
|
|
'')
|
|
:
|
|
;;
|
|
*)
|
|
echo "ERROR: Unknown or unexpected argument \"$1\"; aborted." >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
if test "$#" -gt 0 ; then
|
|
shift 1
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
|
|
##
|
|
# Main Program
|
|
|
|
if ! test -t ; then
|
|
echo "ERROR: Not connected to a terminal; aborted." >&2
|
|
exit 1
|
|
fi
|
|
|
|
tmp_old=$(mktemp)
|
|
tmp=$(mktemp)
|
|
|
|
trap at_int INT
|
|
trap at_exit EXIT
|
|
|
|
printf 'flush ruleset\n\n' > "$tmp"
|
|
|
|
nft list ruleset >> "$tmp"
|
|
|
|
cat "$tmp" > "$tmp_old"
|
|
|
|
while true ; do
|
|
editor=${EDITOR:-vim}
|
|
|
|
"$editor" "$tmp"
|
|
|
|
if diff "$tmp_old" "$tmp" ; then
|
|
echo "No changes."
|
|
|
|
if "$config" ; then
|
|
if store_config "$tmp" "$config_file" ; then
|
|
echo "Stored unchanged ruleset to config_file=\"$config_file\"."
|
|
else
|
|
echo "ERROR: Storing unchanged ruleset to config_file=\"$config_file\" failed." >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
exit 0
|
|
else
|
|
if ! "$yes" ; then
|
|
read -p "Apply these changes? (Y|n) " -r answer
|
|
|
|
case "$answer" in
|
|
''|y|Y)
|
|
:
|
|
;;
|
|
*)
|
|
echo "Aborting (user request)."
|
|
exit 1
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
if ! nft -c -f "$tmp" ; then
|
|
if "$fail" ; then
|
|
exit 1
|
|
else
|
|
read -p "Errors in changes detected (Ctrl-c to abort, ENTER to continue editing)" -r answer
|
|
fi
|
|
elif ! nft -f "$tmp" ; then
|
|
if "$fail" ; then
|
|
exit 1
|
|
else
|
|
read -p "Errors when applying changes (Ctrl-c to abort, ENTER to continue editing)" -r answer
|
|
fi
|
|
else
|
|
if "$timeout" ; then
|
|
set_timeout "$tmp_old" "$timeout_secs"
|
|
fi
|
|
|
|
echo "Changes applied successfully."
|
|
|
|
if "$config" ; then
|
|
if store_config "$tmp" "$config_file" ; then
|
|
echo "Stored changed ruleset to config_file=\"$config_file\"."
|
|
else
|
|
echo "ERROR: Storing changed ruleset to config_file=\"$config_file\" failed." >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
|
|
##
|
|
# Exit Codes
|
|
# - 0: Success: No changes to apply or changes applied successfully.
|
|
# - 1: Error: Usage error, failed or aborted changes or failureto store configfile.
|