コンテナの「読み取り専用」は妥協なき防壁——バックドアを無効化する実戦的ハーデニング
現場のエンジニア諸君、お疲れ様。今日も「動けばいい」という甘い誘惑と戦っていることだろう。
多くのエンジニアがコンテナ化を完了させて満足しているが、実はそのコンテナの多くは「鍵のかかっていない金庫」と同じだ。万が一、アプリケーションに脆弱性(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 を見てエラーが出る場所を特定することから始めてほしい。運用に乗せるための泥臭い調整こそが、真のエンジニアリングだ。
セキュリティは「魔法の杖」ではなく、「規律」だ。コンテナに余計な書き込みをさせない。この単純だが強力なルールを守るだけで、君たちのシステムは格段に硬くなる。準備ができたら、今すぐプルリクエストを出そう。現場からは以上だ。
コメント