ログイン時にジョブが実行され、ログにはクライアントの起動が残るのに、トンネルは上がらない。あるいは上がった直後に数秒で消える。どちらの場合も、プロパティリスト自体はおそらく正しい。launchd はジョブを引き受けたとき、二つの約束をした:プロセスを監督すること、そしてそのプロセスが動き続けることだ。VPN には両方が必要であり、正しい XML ファイルはどちらも提供しない。

有効なプロパティリストは、動作するサービスではない

plutil -lint が検査するのは構造だけだ。バイナリが存在するか、このクライアント版で引数がサポートされるか、認証情報が使える文脈でジョブが始まるかは分からない。それらは意味論的な問いであり、launchd の問題に見える失敗のほとんどはここに由来する。

plutil -lint /Library/LaunchDaemons/com.example.vpn.plist
launchctl print system/com.example.vpn

ジョブが読み込まれたと決めつけず、二番目のコマンドの出力を読むこと。最後の終了ステータスとサービスの稼働状況が示されるので、プロファイルに触れる前に「一度も起動していない」のか「起動して落ちた」のかを区別できる。

エージェントはユーザーセッションを継承し、デーモンは継承しない

Apple の launchd ドキュメントは、ユーザーごとのエージェントとシステムデーモンを区別している。エージェントはログイン中のユーザーの文脈で、そのユーザーのキーチェーンと環境とともに動く。デーモンはそのセッションなしで動き、ジョブをシステムディレクトリへ移してもユーザーの対話的な認証情報が再現されるわけではない。

設計を決めるのはこの違いだ。接続に人のログインや MFA プロンプトの承認が必要なら、サポートされる構成はログイン後に実行されるユーザーセッションのジョブだ。誰もログインしていない状態で動かす必要があるなら、クライアントには独自の更新と失効の手順を備えた承認済みの無人 ID が必要になる。誤った行を選ぶと、きれいに起動して最初の認証段階で失敗するジョブができる。

引数配列にパスワードを書いたり、証明書検証を無効にしたりして、この違いを覆い隠してはいけない。どちらも認証要件を読めるファイル内の恒久的な秘密に変えてしまい、後者はトンネルが応答するあらゆる相手を認証することを意味する。

プロセスが前面に留まる場合にのみ、監督は機能する

自らデーモン化するクライアントは制御を即座に launchd へ返し、監督側はすでに終了したプロセスを見張ることになる。トンネルは上がっているかもしれないが、launchd はジョブが失敗したと判断し、再起動ポリシーは意図しない動作をする。

クライアントが文書化している前面モードまたはサービスモードを使うこと。製品に独自のサポートされた起動機能があるなら、その機能は前面に留まる方法とネットワークスタックへ渡す方法をすでに知っている;独自ジョブは別の設計であり、その改善ではない。

ジョブには一意のラベル、意図した実行ファイル、意図的に組んだ引数配列を与えること。配列はシェルのコマンドラインではないので、パイプやリダイレクトは使えず、引用の挙動も異なる。管理者のターミナルで成功するコマンドは、せいぜい予備的な確認にすぎない。

認証情報はプロンプトなしで機能しなければならない

無人運用は認証情報の問題を解決するのではなく、移動させる。誰かが ID を所有し、有効期間を決め、交換方法を文書化しなければならない。有効期限を監視していない証明書は、カレンダーに日付の入った障害になる。

秘密は引数にもジョブの標準出力にも置かないこと。クライアントが保護された保管庫から認証情報を読めるならその経路を使い、ジョブの実行文脈がその保管庫に到達できるか確認する。root で動くデーモンは通常ログインキーチェーンの項目を読めず、これはシステム文脈でしか現れない類の詳細だ。

準備完了の意味を定義し、その場で検証する

起動の成功はプロセスが始まったことを証明する。ネットワークが上がっていること、ゲートウェイが ID を受け入れたこと、プライベートアプリに到達できることは証明しない。他の何かがトンネルに依存しているなら、ジョブの開始時刻ではなく接続状態を反映する準備完了シグナルが必要だ。

再試行は上限を設け、ネットワーク不在と認証情報の拒否を区別すべきだ。失敗のたびに再起動しても期限切れの証明書は直らず、同一の試行で埋まったログが本当の一件を覆い隠す。

ジョブは実際に動く文脈で受け入れること:起動時のネットワークなし、通常のログイン、スリープからの復帰、制御された停止。そのうえでジョブのラベル、クライアントの版、プロファイルのリビジョンをまとめて記録し、次の担当者がかつて動いたファイルではなく追跡可能なサービスを引き継げるようにする。

関連記事