ポートノッキングで少しだけ SSH 接続をセキュアにした

決められた順番でポートノッキングした時だけ、SSH 接続ができるようにしました。

ポートノッキングとは?

決められた順番でポートを叩くことで、あらかじめ設定してあるコマンドを実行する仕組みのことです。
今回は、ポートノッキングされた時にファイアウォール (iptables) に穴を空ける設定をしました。

逃げ道の確保

設定ミスにより、一切 SSH 接続ができなくなるといった事態を避けるために逃げ道を確保しておきます。
複数ポートで sshd を起動する、特定のネットワークからは(ポートノッキングを使わない) SSH 接続を許可するなど設定を行ってください。

knockd インストール

$ sudo apt install -y knockd

knockd 設定

設定(1)

$ sudo vi /etc/default/knockd  
START_KNOCKD=1         #knockd 自動起動
KNOCKD_OPTS="-i ens3"  #ネットワークインタフェース設定、さくらの VPS なら ens3 のはず(?)  

設定(2)
ポートノッキングに 10001/tcp, 10002/tcp, 10003/tcp を使いました。

$ sudo vi /etc/knockd.conf  
[options]  
    logfile = /var/log/knockd.log     #ログ出力設定  
  
[openSSH]  
    sequence    = 10001:tcp,10002:tcp,10003:tcp  
    seq_timeout = 5  
    command     = /sbin/iptables -I INPUT -s %IP% -p tcp --dport 22 -j ACCEPT  
    tcpflags    = syn  
  
[closeSSH]  
    sequence    = 10003:tcp,10002:tcp,10001:tcp  
    seq_timeout = 5  
    command     = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 22 -j ACCEPT  
    tcpflags    = syn  

“sequence” で設定した順番でポートノッキングすると、"command" が実行されます。
[openSSH] で SSH 通信許可ルールを追加して、[closeSSH] で SSH 接続ルールを削除しています。

iptables 設定

iptables で許可していた SSH 接続は REJECT します。

$ sudo vi /etc/iptables/rules.v4  
-A INPUT -p tcp --dport 22 -j REJECT  
$ sudo iptables-restore < /etc/iptables/rules.v4  
$ sudo iptables -L  
.....  
REJECT     tcp  --  anywhere             anywhere             tcp dpt:ssh reject-with icmp-port-unreachable  
.....  

knockd 起動

$ sudo systemctl start knockd.service  

確認

ローカル Mac から動作確認を行います。

knock インストール

ローカル Mac へ knock をインストールします。

$ brew install knock  

動作確認

ポートノッキングする前の状態を確認します。

$ nmap %remote-server%  
PORT     STATE  SERVICE  
22/tcp   closed ssh    

STATE が “closed” になっていて、SSH 接続ができない状態です。
knock コマンドで、ポートノッキングします。

$ knock -v -d 100 %remote-server% 10001:tcp 10002:tcp 10003:tcp  

STATE が “open” になっていることを確認します。

$ nmap %remote-server%  
PORT     STATE  SERVICE  
22/tcp   open   ssh  

ポートノッキングして、ポートを閉じます。

$ knock -v -d 100 %remote-server% 10003:tcp 10002:tcp 10001:tcp  
$ nmap %remote-server%  
PORT     STATE  SERVICE  
22/tcp   closed ssh  

ポートノッキング後に 10 秒間だけポートを空けるように設定

上記の設定だと明示的にポートノッキングしないと、SSH 切断後もポートが空いたままになってしまうので、ポートノッキング時に 10 秒間だけポートを空けるように設定します。
SSH 接続時のポートノッキングは、.ssh/config の ProxyCommand 設定で行えるので、組み合わせることで SSH 接続時のみポートが空くような動作になります。

knockd 設定

サーバ側の knockd 設定を以下のように変更します。

$ sudo vi /etc/knockd.conf  
[options]  
    logfile = /var/log/knockd.log  
  
[opencloseSSH]  
    sequence    = 10001:tcp,10002:tcp,10003:tcp  
    seq_timeout = 5  
    tcpflags    = syn  
    start_command     = /sbin/iptables -I INPUT -s %IP% -p tcp --dport 22 -j ACCEPT  
    cmd_timeout = 10  
    stop_command      = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 22 -j ACCEPT  

ポートノッキングされると、"start_command" が実行され、10 秒 (cmd_timeout) 後に “stop_command” が実行されます。

ssh 設定

ローカル Mac の .ssh/config に “ProxyCommand” を設定することで、SSH 接続直前にポートノッキングを行います。

$ vi ~/.ssh/config  
Host %remote-server%  
  Port 22  
  ProxyCommand bash -c '/usr/local/bin/knock -d 100 %h 10001:tcp 10002:tcp 10003:tcp; sleep 1; exec /usr/bin/nc %h %p'  

このように設定することで、ポートノッキングを意識しないで SSH 接続できます。

knockd がシステムブート時に自動起動しない問題

/etc/default/knockd で自動起動設定したけど、Debian 9 では自動起動しませんでした。
どうやらバグのようです。

ということで対応

$ sudo vi /etc/systemd/system/knockd.service  
[Unit]  
Description=Port-Knock Daemon  
After=network.target  
Documentation=man:knockd(1)  
  
[Service]  
EnvironmentFile=-/etc/default/knockd  
ExecStart=/usr/sbin/knockd $KNOCKD_OPTS  
ExecReload=/bin/kill -HUP $MAINPID  
KillMode=mixed  
SuccessExitStatus=0 2 15  
ProtectSystem=full  
CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN  
  
[Install]  
WantedBy=multi-user.target  

設定を反映させます。

$ sudo systemctl daemon-reload  
$ sudo systemctl enable knockd.service  

これでシステムブート時に自動起動するようになります。

まとめ

ポートノッキングで少しだけ SSH 接続をセキュアにしました。
ポートノッキングはセキュリティ的には気休めに過ぎないので、sshd 設定はこれまで通りセキュアにしましょう。