問題:Root権限の乱用がもたらす代償
Sysadminになりたての頃、私は非常に悪い習慣を持っていました。パーミッションエラーが出るたびに、すぐに sudo を使ったり、rootユーザーで直接実行したりしていたのです。当時は、動けばそれでいいと単純に考えていました。しかしある日、Dockerコンテナにリモートコード実行の脆弱性が見つかりました。デフォルトのroot権限でDockerを実行していたため、攻撃者はコンテナから脱出し、物理サーバーの制御権まで奪ってしまったのです。
これは、最小権限の原則 (Least Privilege) に違反した際の結果を明確に示しています。Nginx、データベース、またはコンテナをrootで実行することは、システムへの扉を全開にしているのと同じです。コード内に小さなバグがあるだけで、ハッカーはOSの最高権限を手に入れ、データを消去したり、仮想通貨のマイニングマルウェアをインストールしたりできるようになります。
従来のRoot権限はなぜ危険なのか?
デフォルトのLinuxカーネルは、「コンテナ内のroot」と「ホスト上のroot」を区別しません。プロセスがユーザーID (UID) 0を持つ場合、カーネルはそれがハードウェアや機密性の高いシステムコールに介入する全権限を持っていると解釈します。
chroot のような分離技術は、ファイルシステムの視点を変えるだけで、ユーザーの権限の本質を変えるわけではありません。プロセスが依然としてUID 0である場合、容易に「コンテナ脱出 (Container Escape)」攻撃を実行される可能性があります。実際、CVE-2019-5736のような脆弱性では、このroot権限を利用してコンテナ内部からホストの実行ファイルを上書きすることが可能でした。
解決策:User Namespaces (UserNS) の仕組み
User Namespacesを使用すると、外部環境のUID範囲をネームスペース内の別のUID範囲にマッピングできます。
自分の小さな王国では「王」(UID 0)であると想像してみてください。しかし、現実の世界に一歩踏み出せば、あなたはただの一般市民 (UID 100000) にすぎません。もしあなたの王国でクーデターが起きても、反乱軍はその狭い範囲内でのみ権力を持つだけです。外部では何の特権も持たないため、メインシステムを攻撃することはできません。
システムの準備状況を確認する
Ubuntu 22.04、CentOS 8+、AlmaLinuxなどのディストリビューションでは、通常この機能がデフォルトで有効になっています。次のコマンドを入力してみてください。
unshare --user --map-root-user --whoami
もし sudo を使わずに画面に root と表示されれば、システムでUserNSを使用する準備が整っています。
User Namespacesを手動で設定する
その本質を理解するために、自動化ツールを使わずに手動で分離環境を構築してみましょう。
1. 副次UID (Subordinate UIDs) の設定
Linuxは、/etc/subuid と /etc/subgid という2つの設定ファイルを通じてマッピングを管理します。各行で各ユーザーに専用のID範囲を割り当てます。
# ユーザー 'vinh' にID範囲を割り当てる
sudo usermod --add-subuids 100000-165535 vinh
sudo usermod --add-subgids 100000-165535 vinh
この例では、ユーザー vinh は外部の65,536個のID(ID 100,000から開始)を管理することが許可されます。これは、他のシステムユーザーと重複しないようにするための標準的な数値です。
2. unshareによる分離空間の作成
次に、仮想的なroot権限を持つ新しいシェルを作成します。
unshare --user --map-root-user --mount --bash
シェル内のIDを id コマンドで確認します:
uid=0(root) gid=0(root) groups=0(root)
0という数字に騙されないでください。/tmp にファイルを作成し、外部の別のターミナルから確認してみてください。そのファイルの所有者は実際にはUID 1000や100000であり、システムのrootでは決してありません。
応用:Rootless DockerとPodmanの実行
実際には、セキュリティを最大限に高めるために、コンテナサービスをルートレスモード (Rootless Mode) に移行することをお勧めします。
Podman:UserNSに最適な選択肢
Podmanは、デーモンに依存しないアーキテクチャのおかげで、DockerよりもスムーズにUserNSをサポートします。一般ユーザー権限でPodmanを使用してNginxコンテナを実行する場合:
podman run -d --name web-secure -p 8080:80 nginx
Nginxプロセスはコンテナ内のポート80にバインドするためにroot権限を必要とします。UserNSのおかげでその権限を持ちますが、ホストマシン上では実際には一般ユーザーのプロセスにすぎません。ハッカーがコンテナを乗っ取ったとしても、/etc/shadow ファイルを読み取ったり、カーネルに干渉したりすることはできません。
実践的な経験と注意点
1. ボリュームの権限エラーの処理
UserNSを使用する際、最も一般的なエラーはボリュームマウント時のパーミッションエラーです。ホスト上のファイルはUID 1000に属していますが、コンテナ内では読み書きのために仮想UID 0が必要になるためです。
ヒント: Podmanでは、マウントパラメータに :U 接尾辞を追加します。これにより、ネームスペース内のID範囲に合わせて自動的に chown が実行されます:
podman run -v ./data:/data:U nginx
2. newuidmap エラーの修正
newuidmap: write to uid_map failed というメッセージが表示された場合は、すぐに /etc/subuid ファイルを確認してください。このエラーは通常、ID範囲を宣言していないか、ユーザー間のID範囲が重複していることが原因です。
3. リソース制限を忘れない
UserNSは権限に関するセキュリティのみを扱います。ハックされたプロセスは、依然としてサーバーのRAMを使い果たす可能性があります。UserNSと Cgroups を組み合わせてリソースを制限することをお勧めします。一方がアクセス権限をブロックし、もう一方がハードウェアリソースの枯渇を防ぎます。
おわりに
IDマッピングの概念があるため、User Namespacesに慣れるまでは少し戸惑うかもしれません。しかし、これはサーバーにとって非常に強力な防御層となります。まずはコンテナをルートレスモードで実行することから始めてみてください。アプリケーションに脆弱性があっても、メインシステムは常に安全であると知ることで、より安心して運用できるようになるはずです。

