はじめに:コンテナ運用に潜む「安全」という名の錯覚
自宅の一室を宿泊客に貸し出す場面を想像してみてください。個室の鍵を渡したはずなのに、その合鍵で玄関ドアも家主の寝室も開けられてしまうとしたらどうでしょうか。もしその客に悪意があったり、外部の侵入者に操られたりすれば、家全体が乗っ取られてしまいます。
Dockerのデフォルト状態では、まさにこれと同じ状況が日常的に発生していますが、見落とされがちです。コンテナ起動時にユーザーを明示的に指定しない場合、デフォルトのプロセスはroot(UID 0)として実行されます。根本的な原因はLinuxカーネルにあります。カーネルはコンテナとホスト間で共有されているため、コンテナ内部のrootは、ホストマシン上のrootそのものなのです。
その危険性は明白です。WebアプリケーションにRCE(リモートコード実行)脆弱性があったり、カーネルの脆弱性(runcのCVE-2024-21626など)を突いたコンテナエスケープが発生したりした場合、攻撃者は即座にホスト全体を掌握できてしまいます。わずか数コマンドでディスク全体が消去されたり、仮想通貨マイニングツールを仕込まれたり、データベースを丸ごとダンプされたりする恐れがあります。
コア概念:User Namespace Remappingとは?
Linuxには、互いに独立した2つの空間の間でユーザーIDをマッピングするUser Namespaceという仕組みが備わっています。
Dockerでuserns-remapを有効にすると、以下のように動作します:
- コンテナ内部では、プロセスは自身をroot(UID 0)であると認識し続けます。そのため、サービスやパッケージマネージャーはPermission Deniedなどの権限エラーを起こさず、正常に動作します。
- ホストマシン側では、カーネルはそのプロセスを管理者権限を持たない一般ユーザー(例: UID 165536)として扱います。
これにより、攻撃者が万が一コンテナから脱出できたとしても、権限のない名もなき一般ユーザーの枠にとどまります。/etc配下の設定ファイルを改ざんすることも、shadowパスワードファイルを読み取ることも、ホストのシステムプロセスに干渉することもできません。
実践手順:userns-remapをステップバイステップで有効化する
先日、Ubuntu 22.04で稼働する社内サーバー12台のセキュリティ監査を実施しました。ファイアウォールはすでに厳重に設定していましたが、コンテナブレイクアウトのリスクを完全に封じ込めるため、userns-remapの導入を決定しました。作業自体は5分もかからずに完了します。
ステップ 1:システムのサブUID/GID範囲を確認する
Linuxでは、各ユーザーにサブID範囲を割り当てるために/etc/subuidと/etc/subgidの2つのファイルを使用します。まずはこれらのファイルが存在するか確認します:
cat /etc/subuid
cat /etc/subgid
ファイルが存在しないか空の場合は、dockremapユーザー用の設定を追加します:
echo "dockremap:165536:65536" | sudo tee -a /etc/subuid
echo "dockremap:165536:65536" | sudo tee -a /etc/subgid
この設定により、dockremapに対して165536から231071までの計65,536個のサブIDが割り当てられます。これで、コンテナ内のUID 0がホスト上のUID 165536に正確に対応付けられます。
ステップ 2:Dockerデーモンを設定する
/etc/docker/daemon.jsonファイルを開きます(存在しない場合は新規作成してください):
{
"userns-remap": "default"
}
"default"を指定すると、Dockerはステップ1で設定したシステムユーザーdockremapと自動的にペアリングを行います。
ステップ 3:Dockerデーモンを再起動する
再起動コマンドを実行して変更を適用します:
sudo systemctl restart docker
重要な注意点: DockerはUIDマッピングに応じた専用ディレクトリ(例: /var/lib/docker/165536.165536/)へイメージの保存領域を切り替えます。そのため、以前実行していた既存コンテナはdocker psに表示されなくなります。焦る必要はありません。この新しいネームスペース環境下でイメージを再度pullまたはbuildするだけで対応できます。
ステップ 4:動作確認と検証
バックグラウンドでテスト用のAlpineコンテナを実行してみます:
docker run -d --name test-remap alpine sleep 3600
コンテナ内部の実行ユーザーを確認します:
docker exec -it test-remap id
内部の出力は通常通りrootとして表示されます:
uid=0(root) gid=0(root) groups=0(root)
次に、ホストマシン側からプロセスをgrepして確認してみましょう:
ps aux | grep "sleep 3600"
ホスト側には次のような結果が表示されます:
165536 18924 0.0 0.0 1596 4 ? Ss 10:15 0:00 sleep 3600
外部から見たプロセスはrootではなく、UID 165536として実行されていることが分かります。権限の完全な分離という目標が達成されました。
まとめ
User Namespace Remappingを活用すれば、アプリケーションの動作に悪影響を与えることなく、コンテナ内のroot権限をホスト上の一般ユーザーレベルへと格下げできます。設定ファイルの編集にかかる時間はわずか5分程度ですが、Linux Capabilitiesの設定やDocker Content Trust (DCT)などと組み合わせることで、インフラ全体に強固な多層防御を構築することが可能です。

