nc -uz を OpenVPN サーバーに対して実行してもほとんど何も測れず、OpenBSD のマニュアルも注意書きで、あのモードの UDP スキャンは何かが待ち受けていてもいなくても成功を報告すると述べています。終了ステータスはリモートの状態ではなくローカルの送信を反映するだけなので、それを信じる監視役は、プロセステーブルがもっともらしく見える限り、死んだサーバーを正常のままにします。置き換えは、プロセス、リスナー、認証、アプリケーションへの到達性が四つの異なる観測である以上、検査が答えるべき問いを決めることから始まります。そのうち一つを名指しする検査は有用ですが、四つすべてを含意する検査は負債です。

UDP 検査が観測できることとできないこと

nc -uz はデータグラムを送って終了し、返すステータスはローカルの送信を表します。閉じたポートは ICMP port unreachable で応答することがありますが、このスキャンモードの文書化された動作はそれを失敗には変換せず、フィルタされたポートはまったく応答を返しません。したがって、呼び出し側にとって沈黙と成功は同じ意味になります。

そのため、この検査はサーバーではなくローカルスタックの試験になります。ホストに到達できること、ポートがバインドされていること、OpenVPN プロセスが動いていること、クライアントが認証できることは確認できません。TCP 接続試験のほうが強力なのは、ハンドシェイクが明確な答えを返すからですが、それは配備が実際に TCP で待ち受けている場合に限ります。同じ番号で検査を TCP に切り替えると、存在しないかもしれないリスナーを試験することになり、UDP のみのサーバーに対しては健全なサービスを失敗させます。

検査を構成されたトランスポートに合わせることが、どちらの結果を信じるよりも先に来ます。UDP の配備に必要なのは別の信号であり、別のプロトコルではありません。

三つの呼び出し側、三つの異なる問い

準備完了は誰が尋ねるかで変わります。クラッシュしたプロセスを再起動するサービス管理者は、インスタンスが起動してリスナーをバインドしたことを知る必要があります。ロードバランサーは、いま到着するクライアントが受け入れられることを知る必要があります。監視システムは、実際のネットワークからの経路が今も許可されたリソースへのセッションを運んでいることを知る必要があります。

これらは同じ検査ではなく、一つの検査で三つすべてを正直に覆うことはできません。まず呼び出し側を選び、次に証拠を選びます。

現在のインスタンスから検査を組み立てる

OpenVPN はインスタンスが初期化を終えると起動行を書き出し、その行はローカルな起動についての有用な証拠になります。先週の成功は一分前の成功とまったく同じに読めるので、追記されるファイルの末尾ではなく現在の起動から読んでください。サーバーはクライアントが一度も接続する前に初期化を終えることもあるので、その行は認証について何も語りません。

起動検査では、プロセスの状態を現在のログの証拠と、構成されたトランスポートに合うリスナーと組み合わせます。プロセスは動いているが初期化を終えていないときに失敗すべきであり、そのためログの問い合わせはこの起動とこのユニットに限定する必要があります。

systemctl is-active openvpn@server
sudo journalctl -u openvpn@server -b --no-pager | grep "Initialization Sequence Completed"
ss -lunp | grep 1194

ユニットがアクティブであること、この起動が初期化行を生んだこと、何かが UDP ポートを保持していること、という三つの出力です。どれもクライアントが接続できるとは言っていません。ステータスページではこれらを分けておき、インシデントが一つの緑の点に潰れないようにします。

意図的に壊して検査を観察する

検査は、失敗するのを見て初めて信頼できます。サーバーを止め、検査を実行し、期待する結果を確認します。そのあとで再起動し、ほかの変更なしに検査が回復することを確認します。

sudo systemctl stop openvpn@server
nc -uz 203.0.113.10 1194; echo "exit=$?"
ss -lunp | grep 1194
sudo systemctl start openvpn@server
ss -lunp | grep 1194

ブロックを行ごとに読みます。netcat コマンドは exit=0 と出力しますが、そのとき何もバインドされておらず、これが一行で示される問題の全体です。最初の ss は何も出力せず、再起動は成功し、二番目の ss が再びリスナーを示します。止めたサーバーに対して検査が通るなら、それはローカルスタックを測っているだけで、ほかの何も測っていません。

逆の失敗も重要です。健全なサーバーに対して失敗する検査は、不要な再起動を引き起こし、再起動のループ自体が一つの障害になります。検査を何かに接続して行動させる前に、両方向を試してください。

ステータス行が語るべきこと

プロセス、リスナー、認証、アプリケーションという四つの境界のうち、各検査がどれを覆うのかを記録します。一つの検査が複数を覆うことはまれで、それらを「up」に圧縮するステータスページは、インシデントのときに重要な違いを隠します。ネットワーク経路には、専用の識別子を持つ実際のクライアントを使い、ハンドシェイクと許可された無害な操作を一つ完了させてから切断します。これで制御チャネル、認証ポリシー、リスナーを一度に鍛えられます。

その識別子は実際の利用者から分けておき、検査に必要な最小限の権限だけを与えます。広いアクセスを持つ準備検査は、便利さのために追加された新しい攻撃面です。認証をやめた検査はクライアントの受け入れをやめたサーバーとまったく同じに見えるので、ほかのアカウントと同じように資格情報をローテーションしてください。

アラートの文面で境界を名指しし、最初の対応者がユニット、ソケット、証明書、経路のどれを見るべきか分かるようにします。何を証明したかを言えない検査は誤った理由で信頼されますが、netcat の検査がそもそも生き残ったのは、まさにそういう理由です。

関連記事