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