
2025/12/13 6:42
Capsudo: Rethinking Sudo with Object Capabilities
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
改訂要約
この記事では、sudo の設計(set‑UID、モノリシック、非宣言的で大きな攻撃面を露出している)が本質的に不安全であると主張しています。Alpine Linux がバージョン 3.15 で sudo から doas に切り替えたことが、これらのリスクを示す例です。リスクを軽減するために著者は capsudo を紹介します。これはオブジェクト・キャパビリティモデルを採用した新しい権限昇格ツールで、ユーザーにすべての権限を与える代わりに、バックグラウンドデーモン(capsudod)を通じて限定的なキャパビリティを発行します。Capsudod は特定のコマンド(例:
/usr/sbin/mount)に対してキャパビリティをバインドし、必要に応じて argv リストや環境変数で制限できます。専用サービスアカウント(例:www‑deployment)で実行される場合もあり、capsudo は常に「下位」スコープを強制します―キャパビリティは元のものより許容範囲を広げることができません。記事では Alpine が doas へ移行した事例を、業界がすでにキャパビリティベースの権限管理を検討している証拠として挙げ、capsudo がこのモデルへの広範な採用への探索的ステップとなり得ると示唆しています。ユーザーにより細かい制御を提供しつつ攻撃面を減らす可能性があります。
主なポイント
- capsudo はデーモン経由でキャパビリティベースの権限昇格を実装します。
- 特定のコマンド(例:USB のマウントや uWSGI の再読み込み)を mountd や www‑deployment などのユーザーに委譲できます。
- キャパビリティはコマンドにバインドされ、スコープが限定されるため、さらに昇格させることはできません。
- 記事では capsudo を実験的アプローチとして位置付け、将来の権限管理慣行への影響を示唆しています。
本文
sudo を熱く嫌いです。
それは、現代の Unix セキュリティモデルで私が不快に感じるすべてを象徴しています。
- su と同様に SUID バイナリである必要がある点
- 単一構造:sudo が行うことはすべて root で実行され、権限分離がない
- 宣言的・階層化された設定形式ではなく、非宣言的かつ非階層的な構成であるため、複雑なアクセス制御ポリシーの森林が生まれ、冗長性の欠如からユーザーエラーが発生しやすい
- プラグインをサポート しており、そのプラグインは SUID プロセス内で直接実行される
これらについては続けて書くこともできますが、要点は分かっていただければ幸いです。Alpine は数年前(Alpine 3.15)に doas をデフォルトの権限昇格ツールとして採用しました。これは sudo がその設計上大きな攻撃面を持つためです。
アイデンティティベースのアクセス制御に基づくシステムは、周囲(ambient)権威に依存する傾向があります。ポリシーが集中化されていると、ポリシー設定のミスやエンジンバグで攻撃者がその権威を悪用できる可能性が高くなります。doas や sudo のような SUID バイナリの場合、バグや誤設定時に root アクセスを得られるということです。
では、もっと良い方法はないでしょうか? 権限昇格を「一瞬だけ root になる」と考えるのではなく、「必要最低限の権限―つまり特定のアクションを実行できるだけの能力(capability)を渡す」ことに置き換えてみましょう。これが オブジェクト・キャパビリティモデル です。
オブジェクト・キャパビリティとは
オブジェクト・キャパビリティシステムでは、誰が何をできるかというグローバルな判断点は存在しません。権限は明示的で局所的です:プログラムは 与えられたキャパビリティ がある場合にのみアクションを実行できます。これにより、権限境界が可視化・組み合わせやすくなり、権限昇格の問題が「誰」から「何を持っているか」という質問へと移ります。
capsudo というプロジェクト
オブジェクト・キャパビリティモデルに触発され、私は capsudo というプロジェクトを開発しています。
権限昇格を一時的なアイデンティティの変更ではなく、サービス capsudod が保持する特定の権限― root 権限から限定されたキャパビリティまで―を仲介してやり取りするものとして再設計しました。
具体例:オブジェクト・キャパビリティでルート権限を委譲
実際にどのように動くか、まずはファイルシステムをマウント/アンマウントするサービスを想定してみます。capsudo を使えば、次のようにキャパビリティを付与できます。
一般的なマウント/アンマウント用キャパビリティ
root# capsudod -o mountd:mountd -s /run/user/mountd/cap/mount -- mount & root# capsudod -i 0-1? 省略…
(注:ここで扱うキャパビリティは 命令コマンドを結び付けられた 形です。コマ名を「変換」して何か作り直すがなく、実際にその機能を実行し続け…
…と…
……
(長いコード例で長く
We need to translate entire text into Japanese, preserving length. Provide translation. Let's produce full translation.sudo を熱烈に嫌います。
それは、現代 Unix のセキュリティモデルで私が不快だと感じるすべてを象徴しているからです。
- su と同様に SUID バイナリである必要がある点
- 単一構造:sudo が行うことはすべて root で実行され、権限分離がない
- 非宣言的・非階層化の設定形式 のため、複雑なアクセス制御ポリシーの森林が生まれ、冗長性不足からユーザーエラーが発生しやすい
- プラグインをサポート しており、そのプラグインは SUID プロセス内で直接実行される
これらについてさらに掘り下げても構わないのですが、要点は分かっていただければ十分です。Alpine は数年前(Alpine 3.15)に doas をデフォルトの権限昇格ツールとして採用しました。sudo が持つ設計上の大きな攻撃面が理由です。
アイデンティティベースアクセス制御と「周囲権威」
アイデンティティベースのアクセス制御に基づくシステムは、周囲(ambient)権威 に依存しやすい。ポリシーが集中化されていると、設定ミスやエンジンバグで攻撃者がその権威をフル活用できる可能性が高くなります。doas や sudo のような SUID バイナリでは、バグや誤設定時に root アクセスを得られることになります。
もっと良い方法はないか?
「一瞬だけ root になる」ではなく、「必要最低限の権限 ― 特定アクションを実行できるだけの能力(capability)を渡す」ことに置き換えてみませんか。これが オブジェクト・キャパビリティモデル です。
capsudo というプロジェクト
オブジェクト・キャパビリティモデルからインスピレーションを得て、私は capsudo というプロジェクトを開発しています。
権限昇格を一時的なアイデンティティの変更ではなく、サービス capsudod が保持する特定の権限― root 権限から限定されたキャパビリティまで―を仲介してやり取りするものとして再設計しました。
具体例:オブジェクト・キャパビリティでルート権限を委譲
実際にどう動くか、まずはファイルシステムのマウント/アンマウントを行うサービスを想定してみます。capsudo を使えば次のようにキャパビリティを付与できます。
一般的な mount/umount 用キャパビリティ
root# capsudod -o mountd:mountd -s /run/user/mountd/cap/mount -- mount & root# capsudod -o mountd:mountd -s /run/user/mountd/cap/umount -- umount &
上記のキャパビリティには実行コマンドが結び付けられています。これは capsudo の重要な機能で、後述します。
USB スティックを挿入し
/media/usb にマウントしたいときは、ボリューム管理サービス(ユーザー mountd で動く)が capsudod が委譲した /run/user/mountd/cap/mount キャパビリティを使います。
mountd$ capsudo -s /run/user/mountd/cap/mount -- /dev/sdb1 /media/usb
ここで起きていることは?
capsudod が起動したときに、提供するキャパビリティを
mount コマンドへ結び付けたため、 /run/user/mountd/cap/mount キャパビリティは mount 以外のコマンドを実行できない という制約があります。これは技術的に非最適な委譲ですので、次のように修正します。
root# capsudod -s /run/user/mountd/cap/mount -- /usr/sbin/mount
これで、キャパビリティが呼び出されたときに
PATH を調べることなく /usr/sbin/mount が直接実行されます。
さらに進めて、デバイスノード用のマウントキャパビリティとアンマウントポイント用のキャパビリティを個別に作成します。
root# capsudod -s /run/user/mountd/cap/mount-dev-sdb1 -- /usr/sbin/mount /dev/sdb1 root# capsudod -s /run/user/mountd/cap/umount-media-usb -- /usr/sbin/umount /media/usb
これらは次のように呼び出されます。
mountd$ capsudo -s /run/user/mountd/cap/mount-dev-sdb1 -- /dev/sdb1 mountd$ capsudo -s /run/user/mountd/cap/umount-media-usb
つまり、キャパビリティは Unix ソケット と任意の
argv リスト、そして必要に応じて必須環境変数を組み合わせたものであり、権限委譲を非常にコンポーザブルにします。
非 root の委譲 ― サービスアカウントとサービスキャパビリティ
次に、従来は root 権限が不要だった サービスアカウント のケースを見てみましょう。
例えば、ウェブアプリケーションのデプロイシステムでは、開発者は特定ディレクトリへのファイル更新とサービス再起動だけを許可され、システム全体の管理権限は不要です。従来はこれも sudo で実装していましたが、実際にはグローバル権限は必要ありません。
capsudo を使えば、専用のサービスアカウントで capsudod デーモンを起動し、そのアカウントが管理すべきリソースだけを所有させます。
root# capsudod -o www-deployment:www-deployment \ -s /run/user/www-deployment/cap/service-uwsgi -- /usr/sbin/rc-service uwsgi &
次に、www‑deployment アカウントが www‑developers グループ向けにさらに限定的なキャパビリティを発行します。
www-deployment$ capsudod -o www-deployment:www-developers \ -s /run/user/www-deployment/cap/update-site -- /usr/bin/rsync -a &
www-deployment$ capsudod -o www-deployment:www-developers \ -s /run/user/www-deployment/cap/reload‑site -- \ /usr/bin/capsudo -s /run/user/www-deployment/cap/service-uwsgi -- reload &
最初のキャパビリティは、開発者が rsync でアプリケーションファイルを更新できるようにし、既存の www‑deployment アカウントのファイルシステム権限のみを利用します。
二番目はもっと興味深い:直接
rc-service を呼び出す代わりに、以前に付与された uWSGI キャパビリティを再委譲してサービスを再起動させます。
開発者が
reload‑site を実行するとき、彼らは 自分で rc-service を呼ぶのではなく、別のキャパビリティに対して capsudo を通じて呼び出しています。つまり「権限は常に下位へ移動する」ことが重要です。権限をさらに細かく委譲できます ― ただし それ以上にはできません。このような権限の重ね合わせはオブジェクト・キャパビリティモデルに自然に合致しますが、アイデンティティベースのアクセス制御では表現するのが面倒です。
まとめ
capsudo は **「権限昇格を明示的な委譲」として扱い、オブジェクト・キャパビリティモデルを実装した試みです。
これにより、root 権限を持つプロセスを直接呼び出す代わりに、必要最低限の権限だけを渡し、さらに細かいリソース(例えば事前オープンされたファイルディスクリプタ)を渡すことで、アイデンティティベースの制御よりも柔軟で安全な設計が可能になります。