VPSファイアウォール:VPNポートだけを開き、その後で証明する
ひとつのポートは公開のままにしなければ、どのクライアントも接続できない。残りは拒否し、転送と入力を分け、事前に試した復旧経路を残しておく。
VPNポートだけを許可するファイアウォールは、ひとつのルールではない。同じポート番号に関わる三つの別々の判断であり、入力チェーンはローカルプロセスに何が届くかを決め、転送チェーンはトンネルクライアントの代わりにホストを通過できるものを決め、NATテーブルは出ていくときの送信元アドレスを書き換える。どれかひとつを間違えても、結果がきれいな障害になることはほとんどない。クライアントは受け入れるが何も読み込まないホストになるか、誰かがチェーンの順序を読むまで閉じているように見えるホストになる。
閉じられない唯一のポート
VPNリスナーは予測できないアドレスからの接続を受け入れなければならないので、定義上パブリックなサービスである。設計が前提にしている唯一の例外がこれだ。OpenVPN用のUDPポートをひとつ、WireGuard用のUDPポートをひとつ、SSL VPN用のTCPポートをひとつ。それ以外はすべてトンネルの内側に属する。つまりアプリケーションサービスは、すべてのインターフェースではなく、トンネルのアドレスかループバックにバインドすべきである。
ただしバインドは強制ではない。0.0.0.0 にバインドされたプロセスは、ルールが許可した場合にだけ到達できる。既定ポリシーはどのルールにも一致しなかった通信の受け皿であり、ポートを支配するルールではなく、drop の上に置いた accept はやはり accept であり、サービスを公開アドレスから外すのは独立した改善であって、チェーンの代わりにはならない。
入力・転送・NATは別の問いに答える
入力通信はホスト自身が宛先なので、VPNリスナーと管理経路はそこに住む。転送通信はホストを通過し、トンネルクライアントはそれと net.ipv4.ip_forward=1 を必要とする。よくある半分壊れた設定は外向きの流れだけを許可して戻りの方向を忘れるので、クライアントはハンドシェイクを終えてから何も読み込まない。確立済みと関連の通信に対する conntrack ルールがその方向を覆う。
NAT は三つ目の問いである。postrouting で書き換えなければ、転送されたパケットはトンネルのプライベートな送信元のまま外に出て、上流で捨てられる。ホストより先のネットワークはプールを戻す経路を知らないからだ。そのルールの送信元プレフィックスは VPN プールに合わせる必要があり、ホスト自身のアドレスではなく、クライアントのサブネットを名指す転送ルールも同じである。
IPv6 は二つ目のポリシーであり、後付けの考えではない。厳格な IPv4 チェーンと開いた ip6tables のポリシーを持つホストは何も閉じていない。サービスはもう一方のファミリーで到達可能なままだ。ip6tables でルールセットを鏡写しにするか、公開インターフェースで IPv6 を意図的に無効にするか、どちらを選んだかを書き残す。
一読で把握できる基準線
drop から始め、ホストに必要なものだけを足す。まずループバックと応答、次に管理経路、次にトンネルリスナー、次にクライアント向けの転送ポリシーだ。
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
iptables -A INPUT -p udp --dport 1194 -m conntrack --ctstate NEW -j ACCEPT
sysctl -w net.ipv4.ip_forward=1
iptables -A FORWARD -i tun0 -o eth0 -s 10.8.0.0/24 -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADEインターフェース名、ポート、プールはプレースホルダーであり、コピーするのではなくサーバーから読む。結果を保存することは書くことと同じくらい重要で、メモリにしかないルールセットは次の再起動で消え、ホストを静かに開け直す。ディストリビューションの仕組みで永続化し、マシンが起動しなくなる日のためにマシンの外に写しを置く。
ホストを失わずに最後のポートを閉じる
設計には落とし穴がある。SSH がトンネル経由でしか存在しないなら、VPN サービスの故障は SSH も連れていく。戻る道は開いたことのないコンソールだけかもしれない。最後の公開ポートを閉じる前に、そのコンソールが存在し実際に使えることを確かめるか、トンネルに依存しない細い管理経路を一本残す。固定アドレスからの SSH、鍵のみ、パスワード認証は無効である。
変更はすでに確立したセッションから適用する。これから絞り込む接続越しではない。誤りが恒久化せず期限切れになるようロールバックを予約する。たとえば以前のルールセットを復元する systemd-run --on-active=10min のジョブだ。最初のセッションを試す間、二本目の SSH セッションは開いたままにする。そのうえでロールバックのジョブが本当に発火するか確認する。試していない復旧は復旧がないのと同じである。
スクリプトではなく経路を検証する
読み込まれたルールを読む。読み込むはずだったファイルではない。許可された外部ホストから、意図したポートだけが応答することを確かめる。トンネルの内側からは、公開するつもりだったサービスに到達できることを確かめる。既定で拒否するポリシーがトンネル通信まで塞いでいると、誰かが使おうとするまで、機能しているロックダウンと見分けがつかない。
iptables -S
iptables -t nat -S
ss -lntup
ip -4 addr show許可したポート、クライアントプール、トンネルインターフェース、管理元、コンソール手順を設定の隣に記録する。次にプールやインターフェース名を変えるときは、フィルタと NAT ルールの両方で合わせなければならない。さもないとホストは誰にも気づかれずに半開きの状態へ戻る。
ルールセットより価値がある習慣がひとつある。変更のたびに、クライアントの要求で転送経路を、二本目のセッションで復旧経路を証明する。使っていないロックダウンは仮説にすぎない。