nftables でサーバー1台を閉じる
このラボはVM上で動きます
ネームスペースsrvがサーバー、cliがクライアントです。ファイアウォールのルールは、srvの中に書きます。VM自身のinputチェーンを閉じると、採点エージェントまでブロックされるので、ルートネームスペースにはルールを入れないでください。
目標
nftablesで「基本は遮断し、必要なものだけを開ける」を作ります。接続追跡でレスポンスのパケットを許可し、ポートと送信元で穴を開け、捨てられるパケットを数え、ルールをファイルに残します。
なぜ重要なのか
ファイアウォールはルールの一覧ではなく、順序とデフォルトです。デフォルトがdropなら書き漏らしたものが遮断され、acceptなら書き漏らしたものが開きます。そして接続追跡を知らないと、「出ていくものは通るのにレスポンスが入ってこない」というルールを書いてしまいます。実際、最もよくある自己ロックアウトは、SSHで接続したままファイアウォールを有効にして、自分のセッションのレスポンスまで止めてしまうことです。nftablesはiptablesの後継で、Kubernetes・Docker・systemdがすべてこの上でルールを作っているので、読めるようになっていないと「誰がこのルールを入れたのか」を探せません。
ステップ
- ネームスペース
cli・srvとvethcli0↔srv0を作成し、10.70.0.2/24・10.70.0.1/24を付けて、pingが通るようにしてください。 srvで、10.70.0.1:80を待ち受けるpython3 -m http.serverと、10.70.0.1:2222を待ち受けるnc -lkを起動してください。srvにテーブルinet fwとinputフックのチェーン(ポリシーdrop)を作成し、cliから何も通らないようにしてください。inputチェーンに、loの許可、ct state established,relatedの許可、icmpの許可の3つのルールを入れて、pingが再び通るようにしてください。tcp dport 80を許可するルールを入れて、cliからWebは通り、2222は引き続き通らないようにしてください。ip saddr 10.70.0.2から来るtcp dport 2222だけを許可するルールを入れてください。- チェーンの末尾に
counter dropのルールを置き、cliからポート3333を叩いたあと、そのカウンターが見えるnft list chainの出力を/root/fw/dropped.txtに保存してください。 srvの全ルールを/root/fw/ruleset.nftに保存してください。nft -c -fで文法チェックが通る必要があります。
参考
- ルールはすべて
ip netns exec srv nft …で書きます。確認はip netns exec srv nft list rulesetです。 - チェーンの作成:
nft 'add chain inet fw input { type filter hook input priority 0; policy drop; }'。 - ルールの例:
nft add rule inet fw input iifname "lo" accept。 - サーバーの起動:
ip netns exec srv setsid python3 -m http.server 80 --bind 10.70.0.1 --directory /root/fw > /root/fw/http.log 2>&1 < /dev/null &。 - よくある間違い1:
established,relatedを書き忘れて、「80は開けたのにレスポンスが来ない」となることです。サーバーが送る側はoutputなので関係ありませんが、サーバーがクライアントとして出ていくときのレスポンスがそれに引っかかります。 - よくある間違い2:
iif "lo"とiifname "lo"。どちらも使えますが、保存されたルールではiifと表示されることがあります。
サーバーとクライアント
ネームスペースcli・srvとvethcli0↔srv0を作成し、10.70.0.2/24・10.70.0.1/24を付けて、pingが通るようにしてください。
cli0はcliで10.70.0.2、srv0はsrvで10.70.0.1です。loまでUPにします。ip netns exec cli ping -c 1 10.70.0.1を実行します。
2つのサービス
srvで、10.70.0.1:80を待ち受けるpython3 -m http.serverと、10.70.0.1:2222を待ち受けるnc -lkを起動してください。
どちらもip netns exec srv setsid … > 로그 2>&1 < /dev/null &で起動します(プレースホルダーはログファイルです)。ip netns exec srv ss -ltnで80と2222がLISTENになっている必要があり、ip netns exec cli curl -s http://10.70.0.1/が何かを返す必要があります。
基本は遮断する
srvにテーブルinet fwとinputフックのチェーン(ポリシーdrop)を作成し、cliから何も通らないようにしてください。
nft add table inet fwのあと、nft 'add chain inet fw input { type filter hook input priority 0; policy drop; }'を実行します。これで、cliからのpingもcurlも、応答なしでタイムアウトになる必要があります(refusedではなく)。
基本の3行
inputチェーンに、loの許可、ct state established,relatedの許可、icmpの許可の3つのルールを入れて、pingが再び通るようにしてください。
nft add rule inet fw input iifname "lo" accept、… ct state established,related accept、… ip protocol icmp acceptの3つです。この3つは、ほぼすべてのファイアウォールの最初の行です。
80だけ開ける
tcp dport 80を許可するルールを入れて、cliからWebは通り、2222は引き続き通らないようにしてください。
nft add rule inet fw input tcp dport 80 accept。確認: curl -s -m 2 http://10.70.0.1/は通り、nc -z -w 2 10.70.0.1 2222は失敗する必要があります。
送信元で絞る
ip saddr 10.70.0.2から来るtcp dport 2222だけを許可するルールを入れてください。
nft add rule inet fw input ip saddr 10.70.0.2 tcp dport 2222 accept。ポートは開けつつ、特定の送信元にだけ許可します。管理用ポートはこのように絞ります。ip netns exec cli nc -z -w 2 10.70.0.1 2222が成功する必要があります。
捨てられるものを数える
チェーンの末尾にcounter dropのルールを置き、cliからポート3333を叩いたあと、そのカウンターが見えるnft list chainの出力を /root/fw/dropped.txt に保存してください。
nft add rule inet fw input counter dropのあと、ip netns exec cli nc -z -w 1 10.70.0.1 3333を1、2回実行します。ip netns exec srv nft list chain inet fw input > /root/fw/dropped.txtのcounter packets Nが0より大きい必要があります。netns内でlogを使うにはnet.netfilter.nf_log_all_netns=1が必要だということも、覚えておいてください。
ルールをファイルに残す
srvの全ルールを /root/fw/ruleset.nft に保存してください。nft -c -fで文法チェックが通る必要があります。
ip netns exec srv nft list ruleset > /root/fw/ruleset.nft。実際のサーバーなら、このファイルが/etc/nftables.confになり、起動時にnft -fで読み込まれます。nft -c -f /root/fw/ruleset.nftが何も出力せずに終われば合格です。