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