長い稼働時間が証明するのは、その時点でデーモンが持っていた権限で起動が成功したことだけです。ここで重要な障害は後から訪れます。新しいクライアントが接続し、失効リストが差し替えられ、補助スクリプトがファイルを書く必要が生じます。いずれも非特権アカウントが行う新たなオープンであり、それぞれ固有の権限の問いとエラーメッセージを持ちます。三つの判断が、動き続ける設定と次のイベントで失敗する設定を分けます。

早く読み込んだものと後から開くものを切り分ける

ディスクリプタは開いたファイル記述へのハンドルであり、アクセスモードはその記述を作った時点で固定されます。後からプロセスのユーザーを変えてもハンドルは閉じられません。だから root で読んだ鍵ファイルは降格後も機能します。モードも変わりません。読み取り用に開いたハンドルが書き込み可能になることはなく、書き込み可能なハンドルも、アカウントがパスへの権利を失っただけで取り消されることはありません。この二つの事実が、これらの報告にある驚くべき挙動の大半を説明します。

もう半分はメモリです。デーモンは証明書を解析し、解析済みの材料を保持してファイルを閉じられます。使われ続けるのはデータであり、開いたファイルではありません。したがって通信が続いていることは、ディスクリプタが開いていることも、パス名に到達できることも証明しません。証明するのは、現在のセッションに必要な材料が以前に得られたということだけです。

後からのオープンは別の取引です。現在の資格情報、現在のセキュリティコンテキスト、現在のマウントビューの下で実行されるため、起動時に読めたパスが次の接続で拒否されることがあります。ローテーションは罠を追加します。同じパス名に新しいファイルを書いても、既存のディスクリプタは古いオブジェクトに付いたままです。だから再読み込みが成功したように見えても、デーモンは前の内容を使い続けている可能性があります。ローテーションが効いたと信じる前に、プロセスが持つ inode とディスク上の inode を比べてください。

三つの実行時読み取りと、それぞれが起きる時を名指しする

三つのリソースは通常、初期化のはるか後に開かれ、それぞれ違う時点で失敗します。特定のクライアントの設定ファイルはそのクライアントが接続したときに読まれるので、他の利用者が動き続ける中で一人だけ壊れることがあります。失効データはピア認証のときと、ファイルが差し替えられたときに見られます。一時ディレクトリは、スクリプトやプラグインが何かを書くときにだけ触られます。

三つはいずれも起動経路には属しません。だから何週間も健全だったサーバーが次の接続で失敗し得ます。この分類は修正の持ち主も示します。最初の二つはデーモンアカウント下の設定に対する読み取り権限であり、三つ目は目的を絞った書き込み可能な場所です。

Path reopened laterWhat triggers the openFirst sign when it fails
Client configuration fileA connection for that client identityOne client rejected while others connect
Revocation dataPeer authentication, including after replacementRevoked and valid peers both refused
Script or plugin temporary directoryA helper needs to create a fileThe helper exits with a path error

persist-key 多くの人が想定するより狭い問いに答えます。これは 文書化された SIGUSR1 と ping-restart のリセットをまたいで鍵ファイルを開いたまま保ちます。そして CCD ファイル、失効データ、補助実行ファイル、プロセスの完全な再起動には何もしません。それらは今も独自のオープン経路をたどり、その時点で動いているアカウントの下で処理されます。

アカウントと、それが実際に持つ見え方を確認する

unit ファイルを信じるのではなく、動いているデーモンにどのアカウントを使っているかを尋ね、その答えを unit が設定している内容と比べます。食い違いは最初に解くべき問題です。以後の権限確認はすべて、プロセスの実際の身元として実行されるからです。次にパスを構成要素ごとにたどります。通過は各階層で評価されるので、通過できないディレクトリの中の読めるファイルは読めないままです。最終的なファイルモードは最後の問いであり、最初の問いではありません。

名前空間とサービスのサンドボックスは、所有権とモードビットでは表せない境界を加えます。シェルで見えるパスが、chroot やマウント名前空間、ホームディレクトリを隠す unit の中では存在しないか読み取り専用かもしれません。セキュリティモジュールがもう一層加わるので、読めそうに見えるファイルも開くときに拒否され得ます。

ps -o user:12,pid,args -C openvpn
systemctl show -p User -p Group -p ProtectHome -p RootDirectory [email protected]
sudo -u openvpn namei -l /etc/openvpn/ccd/alice

管理者のシェルでの読み取り成功は、そうした複合条件を一つも試していません。デーモンアカウントとして、同じ名前空間と同じサンドボックスで検査を実行するか、その結果はサーバーではなく自分のシェルを説明していると受け入れてください。三番目のコマンドはその試験の安価な版です。各構成要素のモードを表示し、デーモンアカウントが通過できない最初の要素で失敗します。

実行時の書き込みには専用のディレクトリを与える

読み取りは目録の半分にすぎません。補助スクリプトが状態ファイルを作る必要があり、プラグインが設定の隣に状態を書く必要があるかもしれません。それらの書き込みは、設定ツリーを書き込み可能にしても賄えません。必要な機能に応じた書き込み権限を特定のディレクトリに与え、説明できる所有者とモードを添えてください。読むだけのものには読み取り専用のアクセスを選びます。

秘密鍵の権限はポリシーファイルと分けて保ちます。リセット後に読み直す必要のある鍵は、接続のたびに読むクライアントファイルとは要件が異なります。両方に一つの緩いモードを与えると、問題が求める以上のアクセスを渡すことになります。所有権を絞った専用のデーモンアカウントは、広いグループより監査しやすいものです。鍵がリセットをまたいで生き残る必要があるとき、 persist-key ディレクトリを広げるのではなく読み直しを取り除きます。

再現できるイベントから判断する

生きているトンネルが証明するのは、コードを通る一本の経路だけです。ファイルを必要とするイベントこそ、関心のあるオープンを覆う唯一の試験です。新しいクライアント接続、差し替えた失効ファイル、許可されたソフトリセット、計画した再起動のいずれであっても同じです。イベントを選び、実行し、最後ではなく最初のエラーを残してください。

ソフトリセットでは、一つのシグナルと unit のログが鍵を読み直したかどうかを示します。シグナルを送り、unit が書いた最後の行を読みます。

systemctl kill -s SIGUSR1 [email protected]
journalctl -u [email protected] -n 40 --no-pager

権限を落としたからといって、侵害されたデーモンがもうファイルを変更できないと結論づけないでください。まだ何ができるかは、残った資格情報、その能力、書き込めるディレクトリ、そしてまだ保持している書き込み可能なディスクリプタ次第です。権限の削減は権限の範囲を狭め、実行時オープンの目録がその程度を示します。その境界を設定の隣に書いておけば、次の人がもう一度発見する必要はありません。

関連記事