午前2時14分の緊急アラート
先週の火曜日、午前2時14分にスマートフォンのアラームが鳴り響きました。私は重い体を引きずってデスクに向かい、本番環境のジャンプホストから送られてくるログが画面を埋め尽くすのを、目を細めて見つめていました。ボットネットが、外注先のために一時的に開放していたSSHポートを嗅ぎつけたのです。わずか数分のうちに、/var/log/auth.log は1秒間に50回ものログイン試行で膨れ上がっていました。たとえ公開鍵認証を強制していたとしても、本番システムがこれほど執拗な攻撃にさらされるのを見るのは、システム管理者にとって最悪の夜を過ごすのに十分な出来事です。
デフォルトポートを22から49221のような高い番号に変更するのは、よくある第一歩です。しかし現実は甘くありません。Masscanのようなツールを使えば、IPv4の全空間を1秒間に2500万パケットという速度でスキャンできてしまいます。「隠蔽によるセキュリティ(Security through obscurity)」は、執拗なスキャナーの前では脆く崩れ去る戦略です。私は単にポートを移動させたかったのではありません。それを「不可視」にしたかったのです。パブリックなインターネットからは完全に存在しないように見え、かつ私が必要なときだけ即座にアクセスできる状態を目指しました。
根本的な問題:デジタル・ビーコン
標準的なネットワークサービスは、接続を受け入れるためにポートを「Open」または「Listening」状態にする必要があります。この状態は、いわばビーコン(目印)です。たとえFail2Banを使用して3回の失敗後にIPをブロックしたとしても、ポートは依然として SYN パケットに応答します。これは攻撃者に対し、そこでどのサービスが動いているかを正確に教えているのと同じです。もし「regreSSHion」(CVE-2024-6387) のようなゼロデイ脆弱性が現れれば、一杯目のコーヒーを飲み干して apt upgrade を実行する前に、公開されたポートが致命的なリスクとなります。
防御策の比較:ポートノッキング vs SPA
私はまず、チームの既存の基準を評価しました。公開鍵認証とFail2Banは優れていますが、サービスを隠すことはできません。最初に検討したのはポートノッキングです。これはファイアウォールを閉じたままにし、特定のシーケンス(ポート7000、8000、9000への順次アクセスなど)を検知したときだけ、特定のIPに対してポート22を開放する古典的な手法です。
しかし、欠点もあります。ポートノッキングはパケットスニッフィングに脆弱です。攻撃者がネットワークパス上にいれば、「秘密のノック」を傍受し、リプレイ攻撃でアクセス権を得る可能性があります。また、パケットの順序が入れ替わりやすい不安定なモバイルネットワークでは非常に不安定で、自分自身のサーバーから締め出されてしまうことも珍しくありません。
最終的に私がたどり着いたのが、Single Packet Authorization (SPA) です。ノッキングとは異なり、SPAは暗号化された、再利用不可能な単一のパケットを使用します。接続試行のシーケンスに依存するのではなく、バックグラウンドのデーモンが libpcap を介して回線を監視し、特定の形式のUDPパケットを探します。検知するとパケットを復号・検証し、数秒間だけ特定のIPに対してファイアウォール(iptables/nftables)を動的に更新します。
解決策:fwknopの実装
業界標準である fwknop (FireWall Knock Operator) を採用しました。これは Rijndael (AES) または GnuPG 暗号化を利用するため、攻撃者が認証トリガーを偽造したりリプレイしたりすることは事実上不可能です。
1. サーバー側の設定
まず、Ubuntuホストにサーバーコンポーネントをインストールしました。最優先事項は、ポート22に対する INPUT チェインのデフォルトポリシーを DROP に設定することです。
sudo apt update
sudo apt install fwknop-server iptables-persistent
設定には、強力な暗号化キーと、HMAC (Hash-based Message Authentication Code) キーの2つが必要です。これらを作成するために、toolcraft.app/ja/tools/security/password-generator のジェネレーターを使用しました。ブラウザ上でローカルに動作するため、機密データがネットワークに流れることはありません。セットアップ用に64文字の文字列を2つ生成しました。
次に、/etc/fwknop/access.conf でアクセスルールを定義します:
SOURCE ANY
OPEN_PORTS tcp/22
KEY YOUR_LONG_ENCRYPTION_KEY
HMAC_KEY YOUR_LONG_HMAC_KEY
FW_ACCESS_TIMEOUT 30
最後に、/etc/fwknop/fwknopd.conf を編集し、正しいインターフェース(通常は eth0 や ens3)でリッスンするように設定します:
PCAP_INTF eth0
2. クライアント側のセットアップ
ローカルマシンに fwknop クライアントをインストールし、繰り返し入力を避けるために認証情報を保存します。
# ローカルマシンでの操作
fwknop -A tcp/22 -D [サーバーのIP] --key-gen --use-hmac --save-rc-stanza
これにより ~/.fwknoprc エントリが生成されます。先ほど作成した64文字のキーで手動更新します。
3. 擬装のテスト
fwknopd を起動し、ファイアウォールでポート22のトラフィックをドロップした状態で、標準的なSSH接続を試みました。接続はタイムアウトになります。完璧です。スキャナーから見れば、サーバーは応答せず、死んでいるように見えます。
そこで、SPAパケットを送信します:
fwknop -n my-production-server
クライアントが送信完了を確認しました。数ミリ秒以内に fwknopd がUDPパケット(通常はポート62201)を捕捉し、キーを検証して、現在のパブリックIPに対して一時的な iptables ルールを挿入しました。
ssh user@server_ip
無事にログインできました。 /var/log/auth.log は静かなままです。ブルートフォース攻撃のノイズも、スキャナーも存在せず、クリーンな接続だけが確立されました。
運用セキュリティの実践
SPAへの移行により、オンコールのストレスは劇的に軽減されました。ここで得られた教訓は2つあります。1つ目は、fwknopd プロセスが停止した場合に備えて、クラウドプロバイダーのコンソールなど、常に予備の「緊急アクセス手段」を確保しておくことです。2つ目は、SPAはSSH鍵の代わりではなく「追加の装甲」であると理解することです。私は今でも、強力なパスフレーズを設定したEd25519鍵を使用しています。変わったのは、私が自分自身を招き入れるまで、SSHゲートウェイがインターネットにとって「ブラックホール」になったという点です。

