【実務・中級編】 ルートレスコンテナ(Rootless Mode)の運用と権限分離 – インフラ・ネットワーク & クラウドセキュリティ防御ガイド

境界防御は死んだ。ならば「脱獄」を物理的に封じろ:Rootlessコンテナが守る最後の砦

エンジニア諸君、今日も泥臭い戦い、お疲れ様。
多くの現場では「Dockerコンテナを使っているから安全だ」という、根拠のない神話がまかり通っている。だが、もし君たちのWebアプリにRCE(リモートコード実行)の脆弱性が一つでもあったらどうなるか?

コンテナ内の root ユーザーは、設定次第でホストOSの root 権限を掌握できる。いわゆる「コンテナ・エスケープ」だ。これが発生した瞬間、君たちのインフラは脆弱な要塞から、攻撃者にとっての「全権管理者用フロントエンド」へと変貌する。

今日は、その悲劇を未然に防ぐための「Rootlessモード」という最強の防具について、現場の知見を共有しよう。

—

1. なぜ「コンテナ内のroot」が危険なのか?(PoC的視点)

攻撃者が狙うのは、コンテナという箱そのものではない。「箱の外への入り口」だ。

もしデフォルト構成のDockerで、コンテナがホストの docker.sock をマウントしていたり、--privileged モードで動いていたりすれば、攻撃者は docker run コマンドをコンテナ内から発行できる。

攻撃者の思考プロセス:
1. Webアプリの脆弱性(OSコマンドインジェクション等)を突く。
2. コンテナ内に侵入する。
3. docker run -v /:/host -it alpine chroot /host を実行し、ホストOSのルートディレクトリをマウントして乗っ取る。

これが成功すれば、もはやファイアウォールもWAFも意味をなさない。だからこそ、「コンテナをそもそもroot権限で動かさない」というRootlessモードが必須となるわけだ。

—

2. 実践:PodmanによるRootless環境の構築

DockerもRootlessモードを提供しているが、設計思想からしてRootlessネイティブな Podman を私は推奨する。デーモン(常駐プロセス)が存在しないため、攻撃対象領域(アタックサーフェス)が物理的に小さいからだ。

設定のステップ

まずは、一般ユーザー権限でコンテナを動かすために、ユーザーIDの割り当てを確認しておく。

# ユーザーのサブUID/GIDが割り当てられているか確認
grep $(whoami) /etc/subuid
grep $(whoami) /etc/subgid
# 出力例: user:100000:65536 (これが無ければ usermod -v 100000-165535 -w 100000-165535 ユーザー名 で設定が必要)

次に、アプリケーションを実行するDockerfileでは、必ず USER 命令を使用して非特権ユーザーを指定する。

セキュアなDockerfileの例

FROM python:3.11-slim

# rootで作業するのはインストールまで
RUN groupadd -g 1000 appuser && \
    useradd -u 1000 -g appuser -s /bin/bash appuser

WORKDIR /app
COPY . .

# 権限を適切に絞る
RUN chown -R appuser:appuser /app

# 以降は絶対にrootユーザーを使わせない
USER appuser

# コンテナ起動時に実行するコマンド
CMD ["python", "app.py"]

—

3. アプリケーション層での徹底した防御(Python実装例)

コンテナをRootlessにしても、アプリケーションが os.system() や subprocess.call() で不用意に外部入力を受け取れば、コンテナ内での被害は避けられない。

NGな例:

# 絶対にやめろ。OSコマンドインジェクションの温床だ。
import os
filename = request.args.get('file')
os.system(f"cat {filename}")

OKな例:

import subprocess
import shlex

# 1. 外部入力を直接コマンドに渡さない
# 2. shlexでエスケープし、リスト形式で渡すことでシェル経由の実行を防ぐ
def safe_read_file(filename):
    # 安全なファイルパスのバリデーションを挟む
    if not filename.isalnum(): 
        raise ValueError("Invalid filename")
    
    # シェルを介さないリスト形式での実行
    result = subprocess.run(["cat", filename], capture_output=True, text=True)
    return result.stdout

—

4. 運用上の鉄則:不要な機能の全廃

Rootless環境を維持するために、以下の設定を徹底してほしい。

1. --privileged フラグは絶対禁止: どんな事情があってもこれを使うなら設計を見直せ。
2. cap-drop の徹底: コンテナに必要な権限だけを付与する。

  • Podman実行時: --cap-drop=ALL --cap-add=NET_BIND_SERVICE

3. 読み取り専用ファイルシステム: コンテナのルートを読み取り専用にする。

  • podman run --read-only --tmpfs /tmp ...

—

最後に:セキュリティは「諦め」の積み重ねだ

「便利さ」と「堅牢さ」は常にトレードオフだ。Rootlessコンテナへの移行は、開発体験(DX)を一時的に下げるかもしれない。しかし、インシデント対応で深夜3時に呼び出され、数千万円の損害賠償とブランド毀損の謝罪会見に追われるよりは、今ここで少しだけ面倒な作業をする方が、エンジニアとしてはるかにコストパフォーマンスが高い。

君たちが書くその一行のコード、その設定ファイルが、会社の未来を守っている。その自覚を持って、今日からRootlessをデファクトスタンダードにしてほしい。

何かあれば、またいつでも相談に来るといい。現場からは以上だ。

コメント

タイトルとURLをコピーしました