Linux環境でKnockdを使ったポートノッキング設定ガイド:実践的なSSHポート隠蔽術

Security tutorial - IT technology blog
Security tutorial - IT technology blog

背景と必要性

深夜2時、アラート通知が鳴り止まない事態に見舞われました。/var/log/auth.logは数百MBにまで肥大化し、見知らぬ無数のIPレンジから60,000件を超えるSSHブルートフォース攻撃が記録されていました。その時痛感したのは、「SSHポートを変更し、パスワード認証を無効化するだけでは不十分だ」という現実でした。

ポート22を2222や58222といったランダムなポートに変更するだけでは、単なる気休めのセキュリティ(Security through obscurity)に過ぎません。Masscanを使えば、わずか3分足らずで全65,535ポートのスキャンが完了してしまいます。その直後からボットネットによる大量のパケットが押し寄せ、帯域幅を圧迫し、システムログを無駄に肥大化させます。

ファイアウォール(UFW/iptables)によるIP制限はどうでしょうか?この対策は、固定IPを持つオフィスなどで作業する場合には有効です。しかし実際のところ、DevOpsエンジニアはカフェで作業したり、ネットワークを頻繁に切り替えたり、スマートフォンのテザリングを利用したりすることが日常茶飯事です。IPアドレスが頻繁に変わる環境で、手動でホワイトリストを更新し続けるのは非現実的です。

そこで登場するのが、Knockdデーモンを利用したポートノッキング(Port Knocking)です。この手法は、高いセキュリティと運用の柔軟性を完璧に両立させます。

仕組みは非常にシンプルです。ファイアウォール上でSSHポートは常にDROP状態に設定されています。攻撃者やボットネットがポートスキャンを実行しても、何のサービスも稼働していないかのように完全に不可視化されます。

一方、Knockdはバックグラウンドでネットワークインターフェースを監視しています。特定のポートとプロトコル(TCP/UDP)の順序で構成されたパケットシーケンス(ノックシーケンス)を送信すると(扉をモールス信号でノックするように)、Knockdがiptablesを呼び出し、送信元IPに対してのみSSHポートを一時的に開放します。そして一定時間(例:30秒)が経過すると、ポートは自動的に再び閉じられます。

本番環境のクラスターで6ヶ月運用した結果、SSHブルートフォースのログは完全にゼロになりました。接続時の追加の手間も、ノック処理にかかるわずか1秒程度です。

インストール

Knockdは非常に軽量なデーモンであり、メモリ消費量はわずか3〜4MB程度です。主要なLinuxディストリビューションのほとんどで利用可能です。

UbuntuまたはDebianの場合、knockdパッケージとファイアウォールルール永続化ツールのiptables-persistentをインストールします:

sudo apt update
sudo apt install knockd iptables-persistent -y

CentOS、RHEL、AlmaLinuxの場合は、EPELリポジトリ経由でインストールします:

sudo dnf install epel-release -y
sudo dnf install knock-server -y

サーバーにアクセスするクライアント端末側にも、knockクライアントをインストールしておきます:

# Ubuntu/Debianクライアントの場合
sudo apt install knockd -y

# macOSの場合(Homebrewを使用)
brew install knock

詳細な設定手順

構築手順は、ベースラインとなるファイアウォールの設定、Knockdでのノックシーケンス定義、サービスの起動の3ステップです。

1. iptablesによるSSHポート遮断の設定

作業を始める前に、現在開いているSSHセッションはそのまま維持してください。設定ミスによる締め出し(ロックアウト)を防ぐため、設定が完了するまでターミナルを絶対に閉じないでください。

# ループバックおよび確立済みの接続を継続して許可
sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -i lo -j ACCEPT

# ポート22へのすべての新規接続を遮断
sudo iptables -A INPUT -p tcp --dport 22 -j DROP

# サーバー再起動後もルールを保持するために保存
sudo netfilter-persistent save

2. /etc/knockd.conf の設定

Knockdのメイン設定ファイルを開きます:

sudo nano /etc/knockd.conf

デフォルトの内容を以下の設定に置き換えます:

[options]
    UseSyslog
    Interface = eth0

[opencloseSSH]
    sequence      = 7412:tcp,8523:tcp,9631:tcp
    seq_timeout   = 10
    tcpflags      = syn
    start_command = /sbin/iptables -I INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
    cmd_timeout   = 30
    stop_command  = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 22 -j ACCEPT

各設定パラメータの意味は以下の通りです:

  • Interface: サーバーのインターネット向けネットワークインターフェース名(ip -br aコマンドで確認、通常はeth0やens3など)。
  • sequence: ノックするポートの順序。推測されないよう独自のランダムなポート番号に変更してください。TCPとUDPを混在させることも可能です(例:7412:udp,8523:tcp,9631:tcp)。
  • seq_timeout: ノックシーケンスを完了するまでの制限時間(秒)。ここでは10秒以内に完了する必要があります。
  • tcpflags = syn: TCP SYNパケットのみを検知し、浮遊する無関係なパケットによる誤作動を防ぎます。
  • start_command: 正しいシーケンスを受信した際に実行するコマンド。%IP%変数にはノック元のIPアドレスが自動的に代入されます。
  • cmd_timeout: ポート開放後、Knockdがstop_commandを実行して開放ルールを削除するまでの待機時間(秒)。事前に接続を確立したSSHセッションは、前述のESTABLISHED,RELATEDルールにより切断されることなく維持されます。

3. Knockdサービスの有効化

Ubuntuの場合、デーモンの自動起動を許可するために/etc/default/knockdを編集します:

sudo nano /etc/default/knockd

次の2行を設定・確認します:

START_KNOCKD=1
KNOCKD_OPTS="-i eth0"

設定後、サービスを再起動して自動起動を有効化します:

sudo systemctl restart knockd
sudo systemctl enable knockd
sudo systemctl status knockd

動作確認と運用

クライアント端末から動作確認を行います。

初期状態でのポート閉鎖確認

まずはNmapを使用してサーバーのポート22をスキャンしてみます:

nmap -Pn -p 22 198.51.100.24

実行結果としてfilteredが返されるはずです。この状態でssh [email protected]を実行しても、応答がなくOperation timed outになります。

ノックの実行と接続

設定したシーケンス通りにノックコマンドを実行します:

knock 198.51.100.24 7412:tcp 8523:tcp 9631:tcp

コマンドは一瞬(約0.3秒)で完了します。直後にSSH接続を試行します:

ssh [email protected]

即座に接続が成功します。30秒経過するとファイアウォールによりポートは再び閉じられますが、すでに確立されたSSHセッションは切断されることなく快適に作業を継続できます。

~/.ssh/configによる自動化

接続のたびに手動でknockコマンドを実行するのは手間がかかります。クライアント側の~/.ssh/configに設定を記述することで自動化できます:

Host my-server
    HostName 198.51.100.24
    User ubuntu
    IdentityFile ~/.ssh/id_rsa
    ProxyCommand knock %h 7412 8523 9631 && sleep 1 && nc %h %p

これで、通常通りssh my-serverと入力するだけで、クライアントが自動的にノックパケットを送信し、1秒待機してからSSH接続を確立してくれます。

サーバー側でのKnockdログ監視

状態の確認や、ノックしてもポートが開かない場合のトラブルシューティングには、systemdのジャーナルログを確認します:

sudo journalctl -u knockd -f

正常なノックシーケンスを受信した場合、次のようなログが出力されます:

knockd[1234]: 203.0.113.50: opencloseSSH: Stage 1
knockd[1234]: 203.0.113.50: opencloseSSH: Stage 2
knockd[1234]: 203.0.113.50: opencloseSSH: Stage 3
knockd[1234]: 203.0.113.50: opencloseSSH: OPEN SESAME
knockd[1234]: opencloseSSH: running command: /sbin/iptables -I INPUT -s 203.0.113.50 -p tcp --dport 22 -j ACCEPT
knockd[1234]: 203.0.113.50: opencloseSSH: command timeout
knockd[1234]: opencloseSSH: running command: /sbin/iptables -D INPUT -s 203.0.113.50 -p tcp --dport 22 -j ACCEPT

もしログがStage 1やStage 2で止まり、OPEN SESAMEまで到達しない場合は、公共Wi-Fiや経由するプロキシによってTCP SYNパケットがドロップされている可能性があります。その場合は、シーケンスをUDPプロトコルに変更するか、seq_timeoutの値を15秒程度に延長して通信の安定性を高めてみてください。

Share: