物理サーバーの nftables 変更を時限ロールバックで保護する

物理サーバーの nftables 変更を時限ロールバックで保護する

物理サーバーの nftables 変更を時限ロールバックで保護する

物理サーバーをリモート管理する際、ファイアウォールの変更で陥りやすい誤判断は、現在の SSH セッションでコマンドを実行できることを、ルールの検証が済んだ証拠と考えてしまうことです。実際には、接続追跡によって既存のセッションだけが維持され、新規接続はすでに遮断されている場合があります。安全に作業するには、まず変更前の状態を保存して復旧を予約し、その後にルールを適用して、最後に独立したログインで管理経路を確認します。

本記事では Debian 12、nftables、systemd を使い、SSH の接続元だけを制限する追加変更を紹介します。AMDSSH サーバーの接続先アドレスと認証情報は、支払い後にコンソールで確認できます。IPv4 が複数ある場合、SSH には最初のアドレスを使用します。この例ではアカウント名やポート番号を固定せず、業務用ポートを含めた包括的なルールも作成しません。

一、まずルールの管理範囲を明確にする

以下の操作には sudo 権限が必要です。また、nftables がインストールされ、ルールがネイティブの nftables で一元管理されていることを前提とします。コンテナネットワーク、仮想化ブリッジ、ほかのファイアウォールツールがルールを動的に生成している場合、ルール全体のスナップショットをそのまま復元してはいけません。ロールバックによって、変更作業中にそれらのツールが追加した内容まで上書きされる可能性があるためです。その場合は、まずルールの管理主体を確認し、該当するツールの変更手順に従ってください。

最初に現在の状態を確認します。

sudo nft list ruleset
sudo systemctl cat nftables.service
command -v nft

既存のルール、起動時に読み込むファイル、nft の実際のパスを確認します。本記事では、独立した inet ops_guard テーブルを使用します。同名のテーブルがすでに存在する場合は用途を確認し、別の作業で使っているルールを上書きしないでください。

ロールバックタイマーが復元するのは、今回変更するファイアウォールルールだけです。ルーティング、SSH サービス、管理用の接続元を同時に変更したことによる障害は修復できません。1 回の変更で調整する要素は 1 つに絞ってください。

実際の SSH 接続パラメーターを取得する

現在リモートログインしている Bash で実行します。接続情報の環境変数が引き継がれない別のセッションに、先に切り替えないでください。

test -n "$SSH_CONNECTION" || exit 1
read -r ADMIN_IP CLIENT_PORT SERVER_IP SSH_PORT <<< "$SSH_CONNECTION"
case "$ADMIN_IP" in
  *:*) printf '%s\n' 'IPv4 SSH session required'; exit 1 ;;
esac
printf 'source=%s destination=%s port=%s\n' \
  "$ADMIN_IP" "$SERVER_IP" "$SSH_PORT"

ここで取得するのは、サーバーから見た接続元アドレスです。オフィスの外向きアドレスや踏み台サーバーのアドレスである可能性があり、作業端末のプライベートアドレスとは限りません。外向きアドレスが変動する場合は、先に安定した管理経路を確保してください。この例では、現在のセッションが IPv4 を使っている必要があります。IPv6 セッションからルールを生成し、そのまま無理に作業を続けないでください。

二、スナップショットを保存して自動復旧を予約する

スナップショットには、既存のルールを消去する命令を含める必要があります。これがないと、復元時に既存のオブジェクトと重複する可能性があります。ディレクトリは管理者だけがアクセスできるようにし、ルールに含まれるアドレス設計を一般アカウントから読み取れないようにします。

sudo install -d -m 700 /root/firewall-change
sudo sh -c '
set -e
umask 077
{
  printf "flush ruleset\n"
  nft list ruleset
} > /root/firewall-change/before.nft
'
sudo nft -c -f /root/firewall-change/before.nft

-c はチェックだけを行い、ルールを適用しません。構文やルールの参照に関する問題は検出できますが、リモートログインが引き続き可能であることまでは確認できません。チェックに失敗したら変更作業を中止し、エラーを無視して続行しないでください。

同名のジョブが残っていないことを確認してから、3 分後の復旧を予約します。

sudo systemctl status ops-firewall-rollback.timer
sudo systemd-run \
  --unit=ops-firewall-rollback \
  --on-active=3m \
  "$(command -v nft)" \
  -f /root/firewall-change/before.nft
sudo systemctl list-timers ops-firewall-rollback.timer

初回の確認でユニットが存在しないと表示されるのは正常です。すでにジョブがある場合は、まず作成された経緯を調べてください。3 分は今回の操作で設定するロールバックまでの待機時間であり、サービスの提供開始時間を保証するものではありません。タイマーの実行が予約されていることを必ず確認してから、ルールの適用に進みます。この間にほかのネットワークルールを変更すると、ルール全体の復元によってその変更も取り消されるため、別の変更は行わないでください。

三、専用テーブルで管理用の接続元を制限する

初めて使用する場合は、空のテーブルを作成します。同名のテーブルが存在しないことを確認した場合に限り、作成コマンドを実行してください。今回の作業用テーブルがすでにある場合は、この手順を省略します。

sudo nft add table inet ops_guard

続いて、適用候補のファイルを生成します。

sudo tee /root/firewall-change/candidate.nft > /dev/null <<EOF
delete table inet ops_guard
table inet ops_guard {
  chain input {
    type filter hook input priority 10; policy accept;
    iifname "lo" accept
    ct state established,related accept
    ip saddr $ADMIN_IP tcp dport $SSH_PORT accept
    tcp dport $SSH_PORT counter drop
  }
}
EOF
sudo chmod 600 /root/firewall-change/candidate.nft
sudo nft -c -f /root/firewall-change/candidate.nft
sudo nft -f /root/firewall-change/candidate.nft
sudo nft -a list table inet ops_guard

ファイル内の削除と再作成は、1 回の nftables トランザクションとしてまとめて反映されます。先にルールを消去し、その後で 1 行ずつ追加する処理ではありません。候補ファイルを読み込むたびに専用テーブルがすでに存在している必要があるため、そのままコールドスタート用の設定として使うことはできません。

このルールは、ループバック通信と確立済みの接続を許可し、現在の IPv4 管理用接続元から実際の SSH ポートへのアクセスを許可したうえで、ほかの接続元からそのポートへのアクセスを拒否します。最後のルールは IPv6 にも一致するため、この例では IPv6 による新規 SSH 接続を許可しません。IPv6 で管理する必要がある場合は、事前に明示的な ip6 saddr の許可ルールを追加し、別途テストしてください。

ほかの業務用ポートは、このテーブルでは制限していません。また、ここでの accept は、ほかのベースチェーンにある drop を無効にするものではありません。新規接続は既存のルールで拒否される可能性があります。この例を包括的なセキュリティ基準と捉えたり、これを根拠に既存の業務用ルールを削除したりしないでください。

四、独立した新規接続で変更の確定を判断する

元のセッションはログアウトせずに維持し、許可した接続元から別の接続を開始します。クライアントで接続の多重化を有効にしている場合は、無効にしてください。そうしないと、新しいウィンドウを開いても、既存の TCP 接続が再利用される可能性があります。

以下のコマンドはクライアント側の Bash で実行し、コンソールに表示されたパラメーターを対話形式で入力します。

read -r -p 'Host: ' HOST
read -r -p 'User: ' LOGIN
read -r -p 'Port: ' PORT
ssh -o ControlMaster=no -o ControlPath=none \
  -p "$PORT" "$LOGIN@$HOST"

検証には、少なくとも次の 3 項目を含めます。

パスワードや鍵の認証エラーが表示されただけでは、接続元の制限が機能していないとは判断できません。どの層で接続が拒否されたのかを確認する必要があります。テストのためにルールを一時的に緩めないでください。

独立したログインに失敗した場合は、その状態を保ってロールバックを待ち、許可ルールを次々に追加しないでください。復旧後、タイマーに対応するサービスのログを確認し、接続元アドレス、ポート、ほかのチェーンの影響を分析します。検証に合格したことを確認したら、タイマーを停止します。

sudo systemctl stop ops-firewall-rollback.timer
sudo systemctl is-active ops-firewall-rollback.timer

復旧用サービスがすでに起動していないことも確認してください。ちょうどタイマーが満了した場合は、現在のルールを読み直す必要があります。停止コマンドが成功しただけで、候補設定がまだ適用されていると判断してはいけません。

五、永続化と起動時の検証を分けて行う

実行中のルールが検証に合格してから、起動時に読み込むファイルを扱います。前の手順で確認したサービス設定に基づき、実際の読み込み先を確認してください。Debian 12 では /etc/nftables.conf が一般的ですが、カスタマイズされたサービスでは異なる場合があります。

ルールが安定し、ほかの管理ツールが書き込んでいないことを前提に、現在のルール全体を flush ruleset を含むファイルに出力します。nft -c -f でチェックし、元の起動用ファイルをバックアップしてから置き換えてください。専用テーブルだけを保存すると、再起動後に既存の業務用ルールが欠落する可能性があります。

永続化は 2 回目の変更として扱い、改めてロールバックを予約して、起動用ファイル全体を適用し、独立したログインのテストを繰り返します。サービスの有効化、設定の置き換え、サーバーの再起動を 1 本のコマンドにまとめないでください。再起動後の検証は別途予定し、ルール、許可した接続元からのログイン、業務用通信を確認します。

最後に、実際の接続元、SSH ポート、候補ファイル、スナップショット、検証結果を記録します。変更を確定できる根拠は「コマンドが成功したこと」ではありません。新規接続が検証に合格し、拒否される経路が想定どおりであり、起動設定からルールを再現できること。この 3 条件がすべて成立している必要があります。

よくある質問

既存の SSH セッションが使えれば、ロールバックを解除できますか?

できません。接続追跡のルールによって既存セッションだけが維持される場合があります。許可した接続元から独立した新規接続を開き、認証の成功を確認してからタイマーを停止します。

この設定は業務用ポートも閉じますか?

閉じません。実際に使用している SSH ポートへの新規接続だけを制限します。他のポートには既存ルールが適用されるため、この例は全面的なデフォルト拒否の設定ではありません。

EPYC 7543P 専用物理サーバー月額 $450.2〜

リソースの検討からサーバー構成へ

サーバー本体とオプションは月額合計でのご請求です。構成ページで明細をご確認いただけます。接続情報は納品後、コントロールパネルで確認できます。