AIエージェントはsudoを実行できず、それがあなたにとっての境界線となります
AIエージェントに本番サーバーのクリーンアップを任せるのは、それがsudoを実行できないことに気づくまでは、危険なことのように聞こえます。権限の境界が、作業を自ずとエージェントセーフなものと人間のみが行うものとに分割します。
AIコーディングエージェントに、ワイプして出荷する前の本番サーバーをSSH経由でディープクリーンするという、実際の仕事を任せました。それには本能的に不安を感じるものです。稼働中のマシンでエージェントが破壊的なコマンドを実行するというのは、まさに人々が警告するシナリオです。実際に起こったのは、エージェントがsudoできないため、作業の危険な半分を全く実行できなかったということでした。権限モデルが、誰もその分割を設計していないのに、仕事をエージェントセーフなレーンと人間専用のレーンに分割したのです。
エージェントが試みたことと、停止した箇所
エージェントはまず、回収可能な不要物から処理しました。コンテナツールを実行して、停止したコンテナ、未使用のイメージ、ビルドキャッシュをパージし、次にホームディレクトリにある古いチェックアウトを削除しました。これらのいずれも権限昇格を必要としません。コンテナのプルーニングはログインユーザーがすでにアクセスできるデーモンソケットを介して行われ、ホームディレクトリ以下のファイルの削除は単純な所有権の問題です。これらはすぐに積み上がりました:
| 回収されたもの | 容量 |
|---|---|
| 停止したコンテナ | 3.4 GB |
| 未使用のイメージ | 34.5 GB |
| ビルドキャッシュ | 15.1 GB |
| ホームディレクトリの不要ファイル | ~1 GB |
ログインユーザーとして実行されるコマンドだけで約39GBが解放されました。次に、リストはサービスレイヤーに移りました。古いsystemdユニットの無効化、ヘッドレスボックスに不要なパッケージのパージ、そしてデフォルトのブートターゲットをデスクトップから変更することです。これらのいずれもがsudoを必要とし、そこでエージェントは停止しました。
その停止は警告や確認プロンプトではありませんでした。エラーを返して他には何もしないコマンドだったのです。プロンプトはエージェントが応答できるものであり、それに応答することは進捗のように感じられます。フォールバックのないエラーは壁です。
なぜエージェントはその一線を超えられないのか
エージェントとrootの間には2つの堅固な壁があり、そのどちらも維持する価値があります。
まず、エージェントがコマンドを実行するシェルには、制御端末がありません。sudoは端末デバイスからパスワードを読み取りますが、何もアタッチされていない場合、暗黙的に標準入力にフォールバックすることはありません。その場で失敗します。sudoのバージョンと設定に応じて、次のいずれかが表示されます:
sudo: no tty present and no askpass program specified
sudo: a terminal is required to read the password
下層のツールはそれぞれ独自の方言で失敗し、各メッセージが真の障害を特定します:
Failed to disable unit: Interactive authentication required.
E: Could not open lock file /var/lib/dpkg/lock-frontend - open (13: Permission denied)
E: Unable to acquire the dpkg frontend lock, are you root?
systemctlはリクエストをpolkit経由でルーティングしますが、polkitは人間に尋ねようとしても、尋ねる相手を見つけられません。aptはdpkgロックを通過できません。なぜなら、ロックファイルがrootによって所有されているからです。同じ壁ですが、それを説明する文章が3つあるだけです。
第二に、そしてより重要なこととして、エージェントにパスワードを与えることは設計上ありえません。プロンプトにrootパスワードを入力できるエージェントは、間違った理由でそれを実行するように説得されうるエージェントです。懸念事項は、不正なモデルではありません。エージェントはそのマシン上のものを読み取ります。ログ、設定ファイル、README、他の誰かが書いたプログラムからの出力などです。そのすべてが入力であり、入力には指示が含まれている可能性があります。エージェントが保持する認証情報は、エージェントが読み取るあらゆるものから到達可能です。エージェントが認証情報を直接扱わないというルールこそが、最初の制限を煩わしいものではなく、機能たらしめているのです。
3番目の理由は、後になって初めて現れます。それは帰属(attribution)です。sudoは、昇格されたすべてのコマンドを、それを呼び出したユーザーとともに認証ログに記録します。人が昇格された部分を実行すると、記録にはその人と正確なコマンドが記録されます。エージェントに対するパスワードレスのルールでは、同じエントリにはサービスアカウント名が記録されるだけで、誰がその実行を決定したかについては何も示されません。
権限モデルによって生じた分割
そのため、クリーンアップは、それらを記述したポリシードキュメントがないまま、自ずと2つの流れに分かれました。
人間は、別のターミナルで ssh host -t sudo ... を使用して、2つ目のレーンを実行しました。ここで -t は、パスワードプロンプトが機能するように、強制的に本物のTTYを割り当てます。エージェントはすでに一括クリーンアップを済ませ、残っているものについてのクリーンなレポートを残していたため、人間の作業は、オープンエンドのセッションではなく、レビュー済みの短い権限昇格コマンドのリストを実行することだけでした。
その順序こそが、模倣する価値のある部分です。時間のかかる作業は調査です。何を安全に削除できるかを把握し、どれだけ解放されるかを測定し、どのユニットがまだ有効かを確認することです。エージェントはそのような作業を得意としており、そのどれにもroot権限は必要ありません。人間に残された作業は、十数行のコマンドを読んで承認することだけです。十数行の権限昇格コマンドを読むのは1分で済みます。オープンエンドのrootセッションを監督するには午後いっぱいかかります。
これが修正すべき制限ではなく、良いデフォルトである理由
エージェントにパスワードなしのsudoルールを渡し、すべての作業を完了させたくなるかもしれません。それに抵抗してください。rootを必要とするコマンドは、サービスの無効化、パッケージの完全削除、マシンの起動時の動作変更など、人間が最初に確認すべきものです。sudoの境界は、まさにそれらのコマンドを人間にルーティングし、可逆的で影響範囲の小さい作業をエージェントに残します。
この境界がその価値を発揮するのは、エージェントが正しい日ではなく、間違った日です。エージェントは、明らかに非常識な間違いではなく、もっともらしい間違いを犯します。例えば、空文字列に解決されたパス変数、ここではライブで指示元のマシンでは古いデータディレクトリを対象としたクリーンアップ、1階層高すぎる場所をルートにした再帰的な削除などです。権限昇格がなければ、これらの試みはローカルで明確に失敗し、最初のroot所有のエントリでPermission deniedを出して停止します。rm -rf /は、意図したことを示すフラグを渡さない限りrmがルートディレクトリを拒否するため、開始すらされません。リスクにさらされたままなのは、ログインユーザーが所有するものです。実際の損害はありますが、回復可能であり、誰かの判断を信頼する必要が生じる前に範囲が限定されます。
これを元に戻さなければ、この利点を無料で得られます。認証情報をエージェントの手に渡さないようにし、包括的なパスワードなしのルールを追加しなければ、この分離は自然に行われます。
- エージェントはログインユーザーの下で、コンテナのクリーンアップ、キャッシュの再利用、ファイルの削除を処理し、触れられなかったものを報告します。
- rootを必要とするものはすべて、構造上、人間のレーンに送られ、エージェントがすでに対象範囲を絞った短いリストに対して、明示的な
ssh -t sudoセッションを介して実行されます。 - 失敗したコマンドが引き継ぎ事項になります。ユニットやパッケージを名指しするエラーは、記憶から書かれた要約よりも正確な作業項目です。
- 一時的な権限昇格を許可しなければならない場合は、恒久的な許可ではなく、そのマシンがあなたの手から離れる前に削除する、範囲が狭く、時間制限のあるルールにしてください。
境界が見た目よりも薄い場所
クリーンアップレーンが sudo なしで機能したのは、ログインユーザーが、通常は docker グループのメンバーシップを通じて、コンテナデーモンソケットにアクセスできるためです。その権限は、見た目よりも root に近いものです。コンテナを起動できる人なら誰でも、ホストのファイルシステムをその中にマウントし、root として書き込むことができます。
docker run --rm -v /:/host alpine rm -rf /host/etc/systemd/system/some.service
つまり、エージェントが突き当たった壁は、それを積極的に回避しようとする何かに対するセキュリティの境界ではなく、ワークフローの境界なのです。それが機能するのは、エージェントがジョブの通常のコマンドを実行し、エラーを受け取り、それを報告するからです。ソケットがそこにあることを知っている攻撃者に対しては、それは機能しないでしょう。
そのことは、両方の方向で重要です。エージェントを信頼できないものとして扱う場合、コンテナアクセスは sudo ポリシーの前に塞ぐべき穴です。ルートレスランタイムを使用するか、必要な呼び出しのみを公開するソケットプロキシを使用します。そして、エージェントが単独で完了できるように権限を広げたいという誘惑に駆られた場合、コンテナアクセスが見た目ほど小さなステップではないことに注意してください。ユーザーを docker グループに追加したり、ホストの名前空間を共有するコンテナ内でエージェントを root として実行したりすると、同じ場所にたどり着きます。2つのレーンは1つにまとまり、無料のレビュー手順はそれらとともに消えてしまいます。
よくある質問
エージェントは単に sudo -S を使用して標準入力からパスワードを読み取ることはできますか?
機械的には可能ですが、それは避けるべき方法です。パスワードを sudo -S にパイプすると、その認証情報がコマンドライン、シェル履歴ファイル、共有マシン上のプロセスリスト、そしてエージェント自身のトランスクリプトに残ってしまいます。人間の時間で5分を要する境界線を、あなたが制御できない4つの場所にrootの秘密情報を置くことと引き換えることになります。
限定的な NOPASSWD ルールが許容されることはありますか?
限定的かつ一時的なものであれば、許容される場合があります。落とし穴は、引数の対象範囲がコマンド名から想像されるよりも広いことです。systemctl に対する NOPASSWD エントリは、マシン上のすべてのユニットを対象とします。ラッパースクリプトに対するものは、そのスクリプトを編集できる者(エージェントである可能性もあります)にroot権限を与えてしまいます。ルールは固定のコマンドと引数に限定し、追加したセッション内で削除してください。
エージェントがコンテナ内で実行される場合、これらのことは当てはまりますか? メカニズムは変わりますが、問題は残ります。多くのイメージ内ではシェルはすでにrootであるため、頼るべきsudoの壁はもはや存在しません。代わりに重要になるのは、何をマウントし、どのケーパビリティを付与したかです。ホストのファイルシステムを読み書き可能でマウントしたコンテナには境界が全くありません。読み取り専用マウント、ドロップされたケーパビリティ、そしてデーモンソケットなしのコンテナは、強力な境界を持ちます。
エージェントが正当な理由でroot所有のデータを必要とする場合はどうすればよいですか? 書き込みではなく、読み取りを許可してください。特定のディレクトリへの読み取りアクセス権を持つグループを追加するか、自分でダンプを収集して渡してください。root権限を必要とするように見えるタスクのほとんどは、何かを変更するのではなく、何かを見る必要があります。そして、読み取りは、安価に共有でき、後で取り消すことができる半分です。
エージェントを本番サーバーに近づけることについて最も安心できたのは、それが構造的にどれほど多くのことができないかを発見したことでした。