リレー経由の公開SSHは、無関係な二つの境界で失敗し、片方を直すともう片方が見えてきます。エッジがストリームを運ぶ代わりにHTTP応答を返すか、リレーがバックエンドの同意していないヘッダーを先頭に付けるかです。どちらを見ているかは三つの条件で決まります:公開レコードが実際に何を指すか、ストリームブロックが上流に何を送るか、SSHクライアントがどの名前を検証するかです。この順に片付けてください。

二つの境界、二つの症状

エッジ側の失敗は、SSHバナーがあるべき場所にHTTPステータスページが届く形で現れます。接続はTCPレベルで完了し、最初のバイトはWebサーバーに属するテキストです。これはプロトコルの不一致であり、パケット消失ではありません。また、エッジがその名前に対して意図的に別のものを提供していることも示します。

上流側の失敗は見え方が違います。接続はsshdに届き、sshdは期待する識別交換ではないバイトを読み、セッションは鍵を一度も差し出さずに死ぬか固まります。

両者は、どちらも接続してから失敗するクライアントを生むため、混同しやすいものです。受け取る最初のプロトコル応答が両者を分けるものであり、だからこそチケットに残すべきです。

PROXYヘッダーはメッセージであり、スイッチではない

ストリームモジュールでは、proxy_protocol on はNGINXにペイロードの前に上流へ向けてPROXYプロトコルヘッダーを書くよう指示します。TCPプロキシを有効にするものではなく、SSHの暗号化とも関係ありません。これは受信者宛てのメモです。受信者が読めなければ、ストリームの先頭でゴミになります。

受信側のサービスはそのヘッダーを意図的に消費しなければなりません。通常のsshdは接続の最初のバイトとしてSSH識別交換を期待するので、想定外の前置きは壊れたサーバーのように見える形でハンドシェイクを壊します。

stream {
    server {
        listen 2222;
        proxy_pass 10.77.0.20:22;
    }
}

この断片は、VPN越しに到達できるバックエンドに対する動作するリレーの全体です。受信者がそのプロトコルを話さないためPROXYディレクティブは現れず、その不在は省略ではなく判断です。

二つの向きも分けてください。listen行にproxy_protocolを設定することは、NGINXが自身のクライアントに先にヘッダーを送ることを期待するという意味であり、通常のSSHクライアントに対して有効にすると、人が普通に解こうとしている問題のちょうど逆を生みます。

公開ホスト名が実際に到達する先

SSHセッションでHTTP 400のページが返るのは、エッジに関する証拠です。経路のどこかがHTTPを話しており、バックエンドで鍵やアカウントをどういじっても変わりません。エッジが片付くまでバックエンドを変えるのをやめてください。

Cloudflareの通常のプロキシレコードは、対応するHTTPとHTTPSのトラフィックを規定のポートで運びます。ホスト名に対してプロキシを有効にしても一般的なTCPリレーにはならず、SSHを443番ポートへ移してもHTTPSにはなりません。

実行可能な向きは二つあります。DNSのみのレコードなら自身のTCPリレーを指せて、公開範囲の判断を自分の手に置けます。あるいは、SSHの伝送を明示的に文書化した製品を使い、その製品自身のオリジン、クライアント、アクセスの設定に従います。

どちらを選んでも、リレーにはバックエンドへのVPN経路、許可された接続、戻り経路が依然として必要です。公開側が動くからといって経路が任意になるわけではありません。

エイリアスでSSHの同一性を保つ

利用者はたいてい既存のホスト名を打ち続けたいと考えます。SSH設定のエイリアスならリレーに何も変えずにそれができ、変更を本来の場所であるクライアント側に留められます。

Host git.example.com
    HostName ssh-entry.example.net
    Port 2222
    User git
    HostKeyAlias git.example.com

HostKeyAliasがないと、OpenSSHは接続先の名前でホスト鍵を探すため、項目が動いて全利用者が初回に鍵変更の警告を見ます。エイリアスを設定すれば、接続はリレーへ向かうままknown-hostsの項目を安定させられます。

このエイリアスはSSHベースのリモートにのみ適用されます。HTTPSのGit URLはこれを参照せず、通常のリモートURLは実際に用意した転送方式と一致しなければなりません。

してはいけないのは、警告を消すためにホスト鍵の検証を無効にすることです。それは転送の修理が触れることを求めたわけではない保護を外すことであり、見た目の不快を実際の露出に変えてしまいます。

各ホップを個別に検証する

公開リスナーを単独で試し、次にリレーホストからリレーとバックエンド間の接続を試し、その後にだけクライアントを端から端まで試します。この三つの試験はそれぞれ別の層を排除します。一つ飛ばすと残り二つの結果は解釈できません。三つの観察、失敗が住みうる三つの場所です。

各結果には宛先アドレス、ポート、最初のプロトコルバイトを添えてください。その三つ組がエッジの誤りとVPNルーティングの問題とSSH認証の失敗を区別し、それが修理と推測の違いです。

最後に、意図した露出を文書化してください。SSHを私設バックエンドへ運ぶTCPリレーは公開境界に意図的に開けた穴であり、他の受信サービスと同じ見直しに値します。

関連記事