こんにちは!インフラやセキュリティの現場にいると、「コンテナを安全に使いたいけれど、どこから手をつければいいのか分からない……」と悩む新人のエンジニアさんによく出会います。
Dockerなどのコンテナ技術は、アプリをサクッと動かせて本当に便利ですよね。でも、「家を建てたはいいけれど、鍵の締め方を誰も教えてくれなかった」という状態のまま本番環境に出してしまうと、ちょっとしたスキから侵入されて大変なことになってしまいます。
今回は、サイバー攻撃者がコンテナを乗っ取ったときに最も嫌がる防衛策、「ルート権限実行の防止」と「ファイルシステムの読み取り専用(ReadOnlyRootFilesystem)化」について、身近な防犯にたとえながら、一歩ずつ優しく紐解いていきましょう!
—
1. 泥棒が家に入ってきたときのことを想像してみよう
まずは、サイバー攻撃と「空き巣(泥棒)」を比べてみましょう。
あなたが新しい家(コンテナ)を建てたとします。この家、初期設定のままだと、「合鍵が玄関のポストに置きっぱなしで、さらに家中の家具や壁に自由に落書き(改変)ができる状態」になっています。
もし、この家に泥棒が侵入してきたらどうなるでしょうか?
1. ポストから合鍵(ルート権限)をゲットして、家の管理者になってしまう。
2. 家の構造を勝手に書き換え、隠し部屋を作って住み着く(永続化)。
3. いつでも自由に家に出入りできるようになる。
恐ろしいですよね。コンテナの世界でも全く同じことが起きます。アプリケーションの脆弱性を突いて攻撃者がコンテナ内に侵入したとき、もしコンテナが「ルート権限(管理者)」で動いていて、かつ「ファイルシステムが書き換え可能」な状態であれば、彼らは勝手に悪意あるプログラムをダウンロードし、しれっと隠しファイルを作って居座ってしまいます。これがセキュリティの世界でいう「永続化」です。
これを防ぐための鉄壁の二大防御が、今回学ぶ「非ルートユーザーでの実行」と「ReadOnlyRootFilesystem」なのです。
—
2. 防御の第一歩:コンテナを「一般人」として動かす(ルート権限実行防止)
デフォルトのDockerは、コンテナの中の作業を「root(最高管理者)」という最強の権限で行おうとします。これは、会社にたとえると、新入社員がいきなり社長のハンコを自由に押せる状態で仕事を始めているようなものです。危険ですよね。
だからこそ、「コンテナの中では、権限を持たない一般ユーザーとして動きなさい」と指示を出してあげる必要があります。
Dockerfileでの設定例
実際に、専用のユーザーを作って切り替える設定を見てみましょう。
# 安全なベースイメージを使用します
FROM alpine:3.19
# 1. 作業用のグループと一般ユーザー(appuser)を新規作成します
# ※システム用のユーザーとして作成(-Dフラグなど)するのが一般的です
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# 2. アプリケーションのファイルを配置するディレクトリを作成し、所有者を一般ユーザーに変更します
WORKDIR /app
RUN chown -R appuser:appgroup /app
# 3. ★ここが重要! 以降の操作やアプリの実行を、先ほど作った一般ユーザーに切り替えます
USER appuser
# 4. アプリケーションを実行するコマンドを指定します
CMD ["./my-application"]
このように USER appuser を明示するだけで、たとえアプリに脆弱性があって侵入されても、システム全体のファイルを書き換えるような「管理者権限の乱用」を防ぐことができます。
—
3. 防御の第二歩:家の中を「下敷きノート」にする(ReadOnlyRootFilesystem)
さて、一般ユーザーで動かすことで、泥棒が管理者になることは防げました。しかし、まだ「家の壁や家具に落書き(ファイルの改変・追加)」ができる状態は残っています。
ここで登場するのが、Kubernetesなどで設定できる ReadOnlyRootFilesystem: true という機能です。
これは、コンテナのルートファイルシステム(根っこのフォルダたち)を、「物理的に書き込みができない、カチカチの読み取り専用(Read-Only)」にしてしまう設定です。例えるなら、すべてのノートを下敷きでガチガチに固めて、ボールペンで文字を書けなくするようなイメージです。
攻撃者はどうなる?
もし攻撃者が侵入してきて、「よし、ここに俺たちの秘密のプログラムを保存しよう!」とファイルを書き込もうとしても、ファイルシステムが読み取り専用になっているため、エラーになって書き込みに失敗します。これによって、コンテナが再起動されたり作り直されたりしたときに、不正なファイルが綺麗さっぱり消え去る(永続化を許さない)環境が作れます。
Kubernetesでのマニフェスト設定例
実務でよく使われるKubernetes(YAML)の設定を見てみましょう。securityContext という安全性を高めるためのブロックで設定します。
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
containers:
- name: my-app-container
image: my-company/my-app:1.0.0
# セキュリティコンテキストの設定
securityContext:
# ルート権限での実行を禁止し、UID 10001 の一般ユーザーとして実行を強制
runAsNonRoot: true
runAsUser: 10001
# ★ファイルシステムを読み取り専用(Read-Only)にする
readOnlyRootFilesystem: true
# 特権コンテナ化(ホストへのアクセス)を禁止
privileged: false
# 不要なLinux能力(Capabilities)をすべて捨て去る
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
—
4. 「読み取り専用」にするとアプリが動かなくなる?(実務での泥臭い対策)
ここで、現場のエンジニアからよくある疑問が出てきます。
「ファイルシステムを読み取り専用にしたら、ログを書き出すアプリや、一時ファイルを保存するアプリが動かなくなっちゃいませんか?」
その通り! アプリケーションの中には、動作中にどうしても一時的なファイル(キャッシュやセッションデータなど)を書き込もうとするものがたくさんあります。
そんなときの「現場の知恵」が、「書き込みが必要な場所だけ、一時的な空き地(Volume)を別途用意してあげる」というアプローチです。
Kubernetesの設定例で見てみましょう。
apiVersion: v1
kind: Pod
metadata:
name: secure-pod-with-storage
spec:
containers:
- name: my-app-container
image: my-company/my-app:1.0.0
securityContext:
readOnlyRootFilesystem: true # 全体は読み取り専用にしつつ…
runAsNonRoot: true
runAsUser: 10001
# アプリがどうしても書き込みたい特定のフォルダだけ、一時領域をマウントする
volumeMounts:
- mountPath: /app/logs # アプリがログを書き出す場所
name: tmp-logs
- mountPath: /tmp # 一時ファイルを置く場所
name: ephemeral-tmp
volumes:
- name: tmp-logs
emptyDir: {} # コンテナが消えると中身も消える、安全な一時ストレージ
- name: ephemeral-tmp
emptyDir: {}
このように、「家全体はポスターすら貼れないコンクリート壁(ReadOnlyRootFilesystem)にするけれど、メモ用紙が必要な机の上(emptyDir)だけはホワイトボードを置いてあげる」という設計にすることで、アプリの利便性を落とさずに、セキュリティを劇的に高めることができるのです。
—
まとめ:一歩ずつ、セキュアなコンテナ使いへ
今回は、コンテナの安全性(セキュリティ)を底上げする以下の2つのポイントを解説しました。
1. ルート権限実行の防止 (USER や runAsNonRoot):
アプリを「一般人」として動かし、システム全体の乗っ取りを防ぐ。
2. ファイルシステムの読み取り専用化 (readOnlyRootFilesystem):
ファイルシステムをカチカチに固めて、攻撃者の永続化(居座り)を防ぐ。必要最小限の場所だけ一時ストレージをあてがう。
セキュリティ対策というと「なんだか難しそう、面倒くさそう」と感じるかもしれませんが、仕組みを紐解いてみると、どれも私たちの日常にある「防犯の知恵」の応用ばかりです。
最初は小さな設定から、あなたの開発するコンテナ環境にもぜひ取り入れてみてください。一歩ずつ、より安全なインフラストラクチャを作っていきましょう!
コメント