一台のホストに二つの管理エントリ、一つのサービスポート、そして両方を助けるはずだったポリシールールがある。ルールは送信元ポート22に一致し、WANのデフォルトしか知らないテーブルへ検索を送るため、VPNエントリは新しいログインへの応答をやめ、直接エントリは動き続ける。セレクタを正しく読めば、不思議な点は何もない。送信元ポートはサービスを識別するものであり、接続が到着した経路を識別するものではない。

二つのエントリ、一つのサービスポート

ホストはLAN側のアドレスとVPNアドレスを持ち、sshdはその両方で待ち受ける。したがって直接セッションとトンネルセッションはサービスポートを共有し、どちらのローカルアドレスが接続を受け取るかだけが異なる。

その違いだけで応答経路を選べるが、ポートベースのルールはまさにその違いを捨ててしまう。どちらのセッションも送信元ポートが22の応答を生成するため、そのポートに一致するルールでは両者を分離できない。両方の検索を同じテーブルへ送り、片方は適合しない答えを受け取る。修正は、実際に異なる属性に一致させることだ。

各エントリに独自のポートを与える方法も機能するが、それを要件とみなすのは設計上の選択であり、SSHの性質ではない。

ルールを読み、次にテーブルを読む

ポリシールールは順序付けられたセレクタであり、使用可能な答えを返す最初のルールで検索が終わる。一覧を順に読み、どのルールが勝ち得るかに注目する。

次にそのルールが指すテーブルを読むのは、正常に解決されるルールが有用に解決されるルールとは限らないからだ。デフォルトルートだけを持つテーブルは、VPN由来の応答をWANインターフェースから外へ送る。後続のテーブルにあるより具体的なルートは決して参照されず、二つ目のテーブルを追加しても何も変わらないことが多いのはそのためだ。検査すべきは選択されたテーブルである。

ip -4 rule show
ip route show table 100
ip route show table main

この二つの出力を合わせて初めて結果を説明でき、どちらか一方だけでは誤った結論に誘う。

ルールを意図するフローに限定する

二つのエントリを分ける属性はローカル送信元アドレスである。そのアドレスに一致するルールは、トンネルセッションだけをプライベートテーブルへ送り、直接セッションはメインテーブルに残す。

そのルールは、プライベートルートとそのサブネットの戻り経路を含むテーブルと組み合わせる必要がある。両方とも、ホスト上のすべてのサービスではなく管理トラフィックに限定する。そのアドレスからの全トラフィックを捕捉するルールは、ホストがそこから送る他のすべてのものもリダイレクトする。ここでは狭さが賢さに勝るのは、次の人がそれを読まなければならないからだ。

sudo ip rule add from 10.90.0.1 lookup 100 priority 100
sudo ip route add 10.90.0.0/24 dev wg0 table 100
ip route get 10.90.5.20 from 10.90.0.1

両方のエントリを一つのローカルアドレスに変換すると、その区別は消え、代わりに接続を意識した状態が必要になる。

リスナー、権限、応答選択を分けて考える

セッションが機能するには三つの別個の条件が真でなければならない:アドレスが存在し、サービスがそこで待ち受け、応答が戻り道を見つけること。ルート変更が直すのは三番目だけである。

リスナーの設定は独自のファイルと独自の再読み込みを持ち、複数の待ち受けアドレスは正常である。認証もまた三番目の判断であり、ルーティングの例外は誰にもアクセスを与えない。ホスト鍵の検証、アカウント認証、送信元制限を両方のエントリに残し、一つの管理経路を通すためにトンネルの他の制御を弱めたい誘惑に抗うこと。

変更する経路に依存しない復旧チャネルを用意するのは、ここでの誤りが修正そのものを締め出すからだ。

古いセッションではなく新しいセッションで証明する

確立済みの端末は、変更前に受け入れられたものだから何も証明しない。意図した各エントリを通じて新しいセッションを開く。

ss -ltnp | grep ':22'
sudo sshd -T | grep -i listenaddress
ssh -v [email protected] true
sudo journalctl -u ssh -n 30

その後VPNの再接続を繰り返して再試行するのは、再接続を生き延びるルートが、再接続が消去するテーブルに追加されたルートとは同じではないからだ。拒否された送信元は許可されたものと別に見るのは、ブロックされた試行が動作中のセッションの陰に隠れないようにするためだ。ローカルアドレス、ルール、テーブルを結果のそばに保存する。代わりにポートを分割する場合は、SSHの限界ではなく運用上の判断として記録する。

関連記事