systemd の堅牢化を OpenVPN の user ドロップに重ねない
最小権限の二つのレシピを組み合わせても、より厳しい一つのレシピにはならない。誰が身元の切り替えを行うかを選び、そのうえで権限をライフサイクル全体に割り当てる。
最小権限の二つのレシピを貼り合わせても、より厳しい配備にはならない。一方のモデルでは、デーモンが権限を持って起動し、初期化の後に非特権アカウントへ降りる。もう一方のモデルでは、サービスマネージャーが最初の命令から制限された身元で起動する。それぞれ単独では機能するが、組み合わせるとどちらの仕事もきれいに果たせないサービスになりがちだ。以下ではこの判断を順にたどる。
二つのモデル、切り替えの所有者は一つ
最初のモデルはデーモンに属する。コミュニティ版 OpenVPN の user と group というオプションは、トンネルと鍵の設定後に身元を切り替え、persist-key と persist-tun はその資源をその後も使い続けられるようにする。
二つ目のモデルはサービスマネージャーに属する。systemd の User= または Group= の設定が、プロセスがどの身元で起動するかを決め、ケイパビリティの指示が何を保持できるかを決める。どちらのモデルを選ぶかは、セキュリティの問いである前にパッケージングの問いであり、パッケージの保守担当者はすでに答えを出している。あなたの仕事は第三の選択肢を考案することではなく、その答えを見つけることだ。
どちらも正当だ。誤りは、両者を一つの設計の層として扱うことだ。二つ目のモデルは、一つ目の身元切り替えを、プロセスにもう許されていない操作にしてしまうことがある。
身元がどこで変わるかを決める
ユニットがすでに専用アカウントで起動しているなら、デーモンに別のアカウントへ変わるよう指示することは、堅牢化されたサービスが決して行うはずのない setuid 遷移を求めることになる。障害は起動時の権限エラーとして現れ、明確なポリシーのメッセージにはならない。 これは欠けたケイパビリティのように読めるため、人は身元の問いではなく、より広い境界集合へと向かってしまう。
何かを編集する前に、インストールされたパッケージが実際に何を同梱しているかを読む。drop-in ディレクトリも含めてだ。一部のユニットは、マネージャーがすでに扱っているためデーモン自身の降下を意図的に省いており、別のユニットは初期化後の切り替えをそのオプションに頼っている。どちらのモデルを引き継いだかを知らずに一方を外すと、動いていたサービスが起動しなくなる。
選んだモデルを一行で書く。マネージャー所有か、デーモン所有か。以後の指示はすべてその文と整合していなければならず、混在したユニットは妥協ではなく不具合として扱うべきだ。
ケイパビリティは上限であり、付与ではない
ケイパビリティの境界集合は、プロセスが最大で何を保持できるかを示す。それは天井であり、そこにケイパビリティを並べてもそれ自体では何も与えない。だからこそ、境界集合が寛大なユニットでもトンネルを作れないことがある。
ケイパビリティを実際にサービスのアンビエント集合へ入れるのは AmbientCapabilities= である。systemd の実行リファレンス は二つの指示を別々に記述しており、この区別がオンラインの紛らわしいケイパビリティ助言の大半を説明する。
ケイパビリティは、失敗している操作がそれを特定してから追加する。境界集合を広げると本当の原因が隠れ、サービスは設計の意図より強い権限のまま残る。天井が仕事を表さなくなるため、ユニットのレビューも難しくなる。本当の不具合を直したら、余分なケイパビリティは外す。
権限をライフサイクル全体に割り当てる
各段階でプロセスが何をすべきかを列挙する。起動、定常状態、再認証、そして解体だ。インターフェースの作成、経路の変更、保護された鍵の読み取り、ヘルパーの呼び出しは、すべて同じ瞬間に、同じ要件の下で起きるわけではない。能力で並べ替える前に、段階で並べ替える。
永続化のオプションは再ネゴシエーションをまたいで資源を保つが、決して持っていなかった権限を与えるわけではない。起動時にプロセスが読めない鍵は、永続化のフラグが何と言おうと、後でも読めないままだ。フラグはすでに機能していたものを保つ。
共有アカウントではなく専用アカウントを使う。OpenVPN の開発元は nobody を汎用の例として使う習慣を戒めている。ホスト上の他のサービスがすでにそのアカウントで動いていることがあり、あると思っている分離はそこにないからだ。
データチャネルのオフロードや、異なるリンクをするビルドなど、局所的な作業負荷を変える機能は、必要な操作をずらすことがある。何かが壊れたときだけでなく、パッケージが変わったときに地図を見直す。
計画を書き留め、復旧アクセスを確保する
配備の記録には、マネージャー、起動時の身元、意図する定常状態の身元、そして各付与とそれを必要とする操作を記す。その記録が、次の変更を実験ではなくレビュー可能なものにする。
編集している間はコンソールへのアクセスを確保しておく。起動に失敗するユニットは遠隔からの修理が難しい。変更を完了とする前に、再起動と完全な再起動を試す。再起動を生き延びたユニットでも、ネットワークが上がる前に失敗することがある。
成功とは、理解された最小権限のライフサイクルであり、たまたま root でない名前で動いているプロセスではない。