Dockerホスト防衛の最前線:rootless Docker と userns-remap で“突破されても致命傷にしない”運用設計

はじめに

Docker を本番で運用していると、どうしても頭をよぎるのが「もしコンテナが侵害されたら?」という問いです。脆弱性はゼロになりません。依存ライブラリ、アプリの実装ミス、設定ミス、サプライチェーン…どこかで穴は空きます。だからこそ本番では、**“侵入されない”だけでなく、“侵入されても被害を限定する”**発想が必要になります。

ここで効いてくるのが Docker ホスト側の保護です。特に次の2つは、比較的現実的に導入できる「被害縮小」策として定番です。

  • rootless Docker:Docker デーモンもコンテナも root を使わない(非特権ユーザーで動く)
  • userns-remap:コンテナ内の root を、ホスト上の非特権 UID/GID に写像(実体は root ではない)

どちらも「コンテナ内 root = ホスト root」という最悪の構図を崩し、ホスト侵害のハードルを上げる方向に働きます。
ただし万能ではなく、ネットワーク、ストレージ、運用負荷、互換性などのトレードオフがあります。

この記事では、rootless Docker / userns-remap の違いと選び方を整理し、ハンズオンで「まず動かして、制約を体感し、運用に落とし込む」までを扱います。


座学

1) なぜ “ホスト保護” が必要なのか

Docker は “隔離” の仕組みを多層で使っています(namespaces / cgroups / capabilities など)。それでも コンテナはVMほど強い境界ではありません。攻撃者目線だと、狙いはだいたいこうです。

  1. コンテナ内で任意コード実行(RCE)を得る
  2. コンテナからホストへ脱出(権限昇格や設定不備を突く)
  3. ホストを取って横展開(同一ホスト上の他コンテナ、ネットワーク、資格情報へ)

ここで最大の怖さは「ホスト root を取られる」ことです。
逆に言えば、ホスト root までの距離を伸ばすだけでも、防御力は大きく上がります。


2) rootless Docker と userns-remap の違い(思想が違う)

両者は似て見えますが、狙っている層が違います。

rootless Docker(“そもそも root を使わない”)

  • Docker デーモン(dockerd 相当)も、コンテナも 非rootユーザーで起動
  • つまり、Docker という仕組み自体がホスト上で強い権限を持ちにくい
  • “Docker を触れる人 = ホスト root に近い” という構図を崩しやすい

一方で、rootless は 制約が出やすいです。
特に「低いポートを使いたい」「高性能なネットワークが欲しい」「既存の運用資産が root 前提」だと壁に当たることがあります。

userns-remap(“root に見えるけど、実体は非root”)

  • Docker デーモンは従来どおり root で動く(ここがrootlessと決定的に違う)
  • ただし、コンテナ内部の UID/GID をホスト上の別 UID/GID 範囲に写像して、ホスト上の実体は非特権にする
  • “コンテナ内 root でファイルを書いても、ホスト側では別ユーザー扱い” になる

userns-remap は 既存 Docker に比較的合わせやすい反面、デーモン自体は root なので、守れる範囲は rootless より狭い、という見方もできます。


3) ざっくり選び方(実務目線)

  • 新規構築 / セキュリティ最優先 / 単一ホスト運用
    まず rootless を検討(ただし制約を許容できるか確認)
  • 既存の運用に乗せたい / 互換性を保ちたい / 段階導入したい
    userns-remap が現実的(移行の衝撃が比較的小さい)
  • 結論としては
    “理想は rootless、現実は userns-remap から始める” のパターンが多いです。
    ただ、どちらが正しいではなく「あなたの要件で落とし所がどこか」です。

4) rootless の代表的な制約(導入前に知っておく)

  • 特権操作が難しい--privileged やホストに強く触る系は相性が悪い
  • 低いポート(例: 80/443)の扱い:非rootは通常バインドできない
    • 回避策:ホスト側で 8080 受けてリバプロ / 80/443 は別プロセスで受ける など
  • ネットワークが user-space 寄りになりがち(slirp4netns 等)
    • 高スループットが必要だと検討が必要
  • ストレージドライバ:環境によっては fuse-overlayfs 前提になることがある
  • systemd での常駐管理:ユーザー単位の systemd(systemctl --user)が前提になりやすい

5) userns-remap の代表的な落とし穴

  • ボリューム/ホストマウントの権限問題が起きやすい
    • “コンテナ内 root” がホスト上では別 UID になるため、既存ディレクトリに書けない等
  • 既存コンテナ/イメージ/ボリュームの扱いに注意
    • 有効化で挙動が変わるため、切り替えは計画的に
  • トラブル時の切り分けが少し難しくなる
    • ホスト上のファイル所有者が見慣れない UID になる、など

ハンズオン

ここからは “体感” するパートです。
やることは2本立てです。

  1. rootless Docker をユーザー単位で動かす
  2. userns-remap を有効化して UID 写像を確認する

どちらも本番ホストでいきなりやらず、まず検証環境で試してください(切り替えの衝撃があるため)。
コマンドは Linux(Ubuntu/Debian系)を想定しつつ、概念は他ディストリでも同じです。


A. rootless Docker を動かす(ユーザー単位)

1) 事前確認:サブUID/GIDが割り当てられているか

rootless は subuid/subgid を使います。まず確認。

id -u
id -g
cat /etc/subuid | grep -E "^$(whoami):" || true
cat /etc/subgid | grep -E "^$(whoami):" || true

もし割り当てが無い場合は(例:youruser:100000:65536 を追加):

sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 "$(whoami)"

ログアウト/ログイン(または再起動)して反映させます。

2) rootless セットアップ(代表的な流れ)

環境によりパッケージ名は差がありますが、概念は「rootless 用のセットアップツールで導入 → ユーザー systemd で常駐」です。

dockerd-rootless-setuptool.sh install

成功すると、ユーザーサービスが作られます。起動確認:

systemctl --user start docker
systemctl --user status docker --no-pager

3) rootless の docker CLI を使う(ソケットの向き先)

rootless はユーザーのランタイムディレクトリ配下にソケットができます。

export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
docker info | head -n 30

ここで rootless っぽい情報が出ればOKです。

4) 動作確認:コンテナを起動して“ホスト側権限”を意識する

docker run --rm -it alpine:3.20 sh -lc 'id && whoami && echo hello'

「コンテナ内は root に見える」ことが多いですが、重要なのは ホスト側の実体が非rootに寄る点です。
(ここが “root で見えるけど root じゃない” の世界観です)

5) ポートの制約を体感する

rootless で 80 を直接 bind しようとすると、通常はできません。

docker run --rm -p 80:80 nginx:alpine

うまくいかない/制約に当たる場合は、実務では次のように設計します。

  • rootless コンテナは 8080/8443 で待ち受け
  • ホスト側の root 管理プロセス(Nginx 等)で 80/443 を受けてリバプロ
  • あるいはロードバランサで 80/443 を終端して高ポートへ転送

rootless の採用は「アプリのポート設計」まで影響することがある、というのがここでの学びです。


B. userns-remap を有効化して UID 写像を確認する

こちらは Docker デーモンの設定変更です。検証環境で実施してください。
有効化後、既存のコンテナ/ボリュームの扱いが変わる可能性があります。

1) remap 用ユーザーの確認

Docker には dockremap を使う代表例があります。存在確認:

getent passwd dockremap || true

無ければ作成(環境によりパッケージが用意する場合も):

sudo useradd -r -s /usr/sbin/nologin dockremap

サブUID/GID を割り当て:

echo "dockremap:100000:65536" | sudo tee -a /etc/subuid
echo "dockremap:100000:65536" | sudo tee -a /etc/subgid

2) Docker デーモン設定に userns-remap を追加

/etc/docker/daemon.json を編集します。

sudo mkdir -p /etc/docker
sudo sh -lc 'cat > /etc/docker/daemon.json <<EOF
{
  "userns-remap": "dockremap"
}
EOF'

Docker 再起動:

sudo systemctl restart docker
sudo systemctl status docker --no-pager

3) 写像が効いているか確認

コンテナを起動し、ホスト上のファイル所有者を確認します。

docker run --rm -d --name ns-test alpine:3.20 sh -lc 'sleep 300'
docker exec -it ns-test sh -lc 'id; touch /tmp/hello; ls -ln /tmp/hello'

次にホスト側でコンテナの実体を探すのは少し面倒ですが、ポイントはここです:

  • コンテナ内で root が作ったファイルでも
  • ホスト上では 100000 番台などの別 UID になっていることがある

(これが userns-remap の “被害縮小” の核です)

4) よくある権限問題を再現して理解する(ホストマウント)

例えばホスト側のディレクトリをマウントして書き込み:

mkdir -p /tmp/remap-vol
chmod 755 /tmp/remap-vol

docker run --rm -it -v /tmp/remap-vol:/data alpine:3.20 sh -lc 'touch /data/x && ls -ln /data'

ここで「書けない」「所有権が変」などを体感するはずです。
userns-remap を採用すると、ホストマウントの権限設計が必須になります。
(“今まで動いてたからOK” が通用しなくなる代表例)


まとめ

rootless Docker と userns-remap は、どちらも Dockerホスト侵害リスクを下げる有力な手段です。ただし、目的と副作用が違います。

  • rootless Docker:Docker の実行基盤そのものを非root化し、より強く「ホスト root に近づきにくい構造」を作る
    • その代わり、ネットワーク/ポート/互換性で制約が出やすい
  • userns-remap:既存 Docker の互換性を保ちつつ、コンテナ内 root をホスト上では非特権にして被害を縮小する
    • その代わり、ボリューム/ホストマウントの権限や移行の設計が重要になる

どちらを選ぶにせよ、共通して大事なのは「入れたら安全」ではなく、運用の形に落とすことです。

  • 低ポートはどう受ける?
  • ボリューム権限はどう設計する?
  • 障害時に誰がどう切り分ける?
  • 移行手順とロールバックは?

“守る” は設定だけでは完結しません。手順と責任分界と設計まで含めて初めて武器になります。


投稿日

カテゴリー:

投稿者:

タグ:

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です