nftablesのルールを安全に更新する
今日は SanjuroE 氏が作成した素晴らしいスクリプトを見つけたので、紹介したい。
Safe reload with nftables
Reducing the risk of getting locked out
https://sanjuroe.dev/nft-safe-reload
background
ファイアウォールの設定変更は、(特にsshなどのリモート接続で行う場合は)危険度が高い操作だ。
iptablesに比較して、nftablesはよりトランザクショナルになり、設定の記述ミスに対する安全性が向上した。
しかし、「正しい記法の誤ったルール」を適用してしまうケースは、これで救済することができない。文法チェックに通ったルールを適用した直後、ssh接続が切れ二度と接続できなくなる体験は珍しくない。今回はこれをどうにかしたい。
proposal
今回の要旨は、設定の誤りを減らすことではなく、誤った設定を投入した後に修正する機会を与えることにある。
ちなみにネットワーク機器において「ネットワーク越しに設定変更しなければならないが、設定に失敗すると二度と接続できなくなる」というシチュエーションは至極一般的だ。Juniper社のルータには、このような状況に応じて commit confirmed というコマンドがある。
これは「新しい設定を適用するが、適用後の一定秒数内に承認コマンドを叩かないと、自動的に元の設定に戻る」というコマンドだ。つまり誤った設定を適用して、リモート接続が切れてしまっても、そのまま一定秒数が経てば自動的に元の設定に戻る。逆に正しい設定が適用されたなら、承認コマンドを叩けばその設定が維持される。
この概念を nftables の操作で実現することで、より安全にルールを変更できるようになる。
method
冒頭の SanjuroE 氏によるスクリプトがこれを実現するものだ。現状のnftablesの設定を一時的に保存してから、 /etc/nftables.conf の内容を改めて適用し、10秒間以内に Y キーで確認操作をしなければ元のnftables設定に戻る。
なおスクリプトエンジンとしてのnftablesは元々トランザクショナルだ。新しい設定ファイルが文法的に正しくない場合は、そもそもファイルの内容が適用されることはない。
modify
個人的にこれを改変して、ルールファイルの指定などを可能にしたものが以下のスクリプトになる。
#!/usr/bin/env bash
# Safely loads nftable rulesets
# Based on a script written by Sanjuro E.
# https://sanjuroe.dev/nft-safe-reload
# Modified by 0x8100
# https://tech.0x5e00.com/blog/2023/09/reload-nftables-safely.html
# so No license can be determined for this file.
#
# Default variables
#
TIMEOUT=10
SAVED_RULES=$(mktemp)
RULES="/etc/nftables.conf"
trap "rm $SAVED_RULES" EXIT
#
# Functions
#
save() {
echo "flush ruleset" > $SAVED_RULES
nft list ruleset >> $SAVED_RULES
}
check () {
nft -c -f $RULES
}
apply() {
nft -f $RULES
}
restore() {
nft -f $SAVED_RULES
}
read_yesno() {
read -t $TIMEOUT yn 2> /dev/null
case "$yn" in
y|Y)
return 0
;;
*)
return 1
;;
esac
}
#
# Parse cmdline
#
usage() {
cat <<EOM
Usage: $(basename "$0") [OPTION] [FILE]
Load nftable rulesets safely.
-d, --default Reload $RULES
[FILE] A nft script what can be read with nft -t
Either -d or FILE must be specified.
EOM
}
if (( $# != 1 )); then
usage > /dev/stderr
exit 1
fi
case "$1" in
-h|--help)
usage
exit 1
;;
-d|--default)
;;
-*)
usage
exit 1
;;
*)
RULES=$1
;;
esac
#
# main routine
#
if (( $EUID != 0 )); then
echo "please run as root. aborting" > /dev/stderr
exit 1
fi
if ! which nft &> /dev/null; then
echo "nft command has not found. aborting" > /dev/stderr
exit 1
fi
if ! check; then
echo "Failed to parse script. aborting" > /dev/stderr
exit 1
fi
if ! save; then
echo "Failed to save current settings. aborting" > /dev/stderr
exit 1
fi
echo "The script is going to update nftables with rules written in $RULES"
echo -n "--> Do you want to continue? [y/N] "
if ! read_yesno; then
echo "Aborted by user (or maybe timed out)"
exit 1
fi
if apply; then
echo "Done. rollback timer has started (${TIMEOUT}s)"
echo -n "--> Do you want to accept the new nftables configuration? [y/N] "
if read_yesno; then
echo "New configuration has been accepted."
else
restore
echo "New configuration has been rejected and the old one restored."
exit 2
fi
fi