【実務・中級編】 コンテナの読み取り専用ルートファイルシステム設定 – インフラ・ネットワーク & クラウドセキュリティ防御ガイド

コンテナの「読み取り専用」は妥協なき防壁——バックドアを無効化する実戦的ハーデニング

現場のエンジニア諸君、お疲れ様。今日も「動けばいい」という甘い誘惑と戦っていることだろう。

多くのエンジニアがコンテナ化を完了させて満足しているが、実はそのコンテナの多くは「鍵のかかっていない金庫」と同じだ。万が一、アプリケーションに脆弱性(RCEなど)が発見された際、攻撃者が最初に行うのは何だと思う?「ファイルの書き込み」だ。彼らは/tmpや/var/www/htmlにバックドアとなるWebシェルを設置し、永続的なアクセス権を確保しようとする。

これに対して、今日解説する「読み取り専用ルートファイルシステム(Read-only Root Filesystem)」は、攻撃者の武器を根こそぎ奪う極めて強力な防御策だ。

なぜ攻撃者は「書き込み」を狙うのか(PoCのリスク)

想像してほしい。君のWebアプリケーションにコマンドインジェクションの脆弱性が残っていたとする。攻撃者は以下のようなコマンドを投げ込むだろう。

# 攻撃者がバックドアを設置しようとする様子
echo '<?php system($_GET["cmd"]); ?>' > /var/www/html/backdoor.php

もしコンテナがデフォルト設定(書き込み可能)なら、このファイルは即座に生成され、攻撃者はブラウザ経由で任意のコマンドを叩き放題になる。一度侵入を許せば、ログを改ざんし、ツールをインストールし、踏み台として利用する。これが「永続化」という最悪のシナリオだ。

ルートファイルシステムを読み取り専用にすれば、このコマンドは Read-only file system というエラーを吐いて拒絶される。攻撃者はメモリ上での生存を強いられ、コンテナが再起動されるたびに全てがリセットされる。これだけで、攻撃の難易度は跳ね上がるんだ。

実装のポイント:すべてを閉じ、必要な場所だけを開く

「読み取り専用」にすると、当然ながらアプリが動かなくなるケースがある。ログ出力、セッション管理、一時ファイルの生成などだ。これらを tmpfs(メモリ上のファイルシステム)を使ってマウントするのが、プロの設計だ。

Kubernetesでの実装例

Kubernetesを使っているなら、securityContext で制御するのが正攻法だ。

# deployment.yaml
spec:
  containers:
  - name: my-app
    image: my-app:latest
    securityContext:
      # ルートファイルシステムを読み取り専用に
      readOnlyRootFilesystem: true
    volumeMounts:
    # 必要なディレクトリだけtmpfsとしてメモリ上にマウント
    - mountPath: /tmp
      name: tmp-volume
    - mountPath: /var/log/app
      name: log-volume
  volumes:
  - name: tmp-volume
    emptyDir:
      medium: Memory # ディスクではなくメモリを使用
  - name: log-volume
    emptyDir: {}

Docker Composeでの実装例

開発環境や単一ホストでの運用なら、以下の設定を試してほしい。

# docker-compose.yml
services:
  web:
    image: my-app:latest
    read_only: true
    tmpfs:
      - /tmp
      - /var/cache/nginx

アプリケーション側の配慮:ログと一時ファイル

設定を適用した後に「アプリがエラーで落ちた」と騒ぐのは、設計ミスだ。開発段階から、以下のような運用を徹底してほしい。

1. ログ出力: 標準出力(stdout/stderr)に吐き出し、Dockerのログドライバー(fluentdやCloudWatch Logsなど)に回収させる。ファイルに書こうとするな。
2. 一時ファイル: どうしても必要な場合は、/tmp を利用する前提でコードを書くこと。

例えば、Pythonでファイル書き込みを行う場合、以下のように設計する。

import os
import tempfile

def process_upload(file_content):
    # ルートディレクトリではなく、必ずtmpfsとしてマウントされた領域を使う
    temp_dir = tempfile.gettempdir() 
    file_path = os.path.join(temp_dir, "uploaded_data.tmp")
    
    with open(file_path, "wb") as f:
        f.write(file_content)
    # 処理終了後は速やかに削除

セキュリティチーフからの助言

「読み取り専用」に切り替える作業は、時としてレガシーなアプリケーションにとって苦痛を伴う。だが、この苦痛を避けることは、将来のインシデント対応という「さらに大きな苦痛」を予約することと同義だ。

まずはステージング環境で readOnlyRootFilesystem: true を設定し、docker logs を見てエラーが出る場所を特定することから始めてほしい。運用に乗せるための泥臭い調整こそが、真のエンジニアリングだ。

セキュリティは「魔法の杖」ではなく、「規律」だ。コンテナに余計な書き込みをさせない。この単純だが強力なルールを守るだけで、君たちのシステムは格段に硬くなる。準備ができたら、今すぐプルリクエストを出そう。現場からは以上だ。

コメント

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