はい、承知いたしました。世界トップクラスのレッドチームエンジニアとして、コンテナのルートファイルシステム書き込み禁止設定について、新人のIT担当者や一般開発者にも理解できるよう、身近な例えを交えながら丁寧に解説するブログ記事を作成します。
—
コンテナを「金庫」にする! readOnlyRootFilesystem で攻撃者の侵入を防ぐ方法
皆さん、こんにちは!セキュリティの世界って、なんだか難しそう…と感じるかもしれませんが、実は身近なことに例えると、意外とスッキリ理解できるものなんですよ。今回は、最近よく耳にする「コンテナ」という技術のセキュリティについて、特に「ルートファイルシステムを書き込み禁止にする」という、とっても効果的な設定について、一緒に学んでいきましょう!
コンテナって、どんな「お家」?
まず、コンテナについて少しおさらいです。コンテナは、アプリケーションとその実行に必要なものをひとまとめにした、いわば「小さな箱」のようなものです。この箱のおかげで、どこでも同じようにアプリケーションを動かすことができるんですね。
例えるなら、コンテナは「あなた専用の小さなアパートの一室」のようなものです。その部屋には、あなたが住むために必要な家具(アプリケーション)や、電気・水道(実行環境)がすべて揃っています。
攻撃者は「泥棒」? 狙われる「部屋」の中身
さて、この「アパートの一室」に、もし悪意のある「泥棒」が侵入してきたらどうなるでしょう? 泥棒は、部屋の中に新しい「怪しい道具」を置いたり(マルウェアの配置)、部屋の構造を勝手に変えて、いつでも戻ってこれるようにしたり(永続化)するかもしれません。
コンテナも同じで、もし攻撃者に侵入を許してしまうと、中に置かれているデータが盗まれたり、不正なプログラムを仕掛けられたりする危険があるんです。
「鍵」をかける場所:ルートファイルシステム
コンテナの中には、「ルートファイルシステム」と呼ばれる、OSの基本的なファイルやディレクトリが格納されている、いわば「部屋の土台」のような部分があります。もし、この土台に泥棒が何かを仕掛けられたら、それはとても危険な状態ですよね。
readOnlyRootFilesystem で「金庫」にする!
そこで登場するのが、今回ご紹介する readOnlyRootFilesystem という設定です。これは、コンテナのルートファイルシステムを「読み取り専用」にする、という指示なんです。
これを家の鍵に例えてみましょう。
- 通常のコンテナ: 玄関のドアは開いていて、誰でも自由に出入りして、部屋の中に新しいものを置いたり、家具を移動させたりできます。
readOnlyRootFilesystemを有効にしたコンテナ: 玄関のドアには「鍵」がかかっていて、さらに「この部屋は、物を置いたり移動させたりできません」という注意書きがあるような状態です。基本的には、中にあるものを「見る」ことしかできません。
これなら、泥棒が勝手に新しい「怪しい道具」を置いたり、勝手に部屋の構造を変えたりすることができなくなりますよね! つまり、マルウェアの配置や、攻撃者による永続化を防ぐことができる、非常に強力な防御策なんです。
どうやって設定するの? Docker Compose の例を見てみよう!
この readOnlyRootFilesystem 設定は、コンテナを起動する際に指定します。ここでは、よく使われる Docker Compose を使った設定例を見てみましょう。
例えば、以下のような docker-compose.yml ファイルがあるとします。
# docker-compose.yml
version: '3.8'
services:
my_app:
image: my_app_image:latest
# ここで readOnlyRootFilesystem を true に設定します
readOnlyRootFilesystem: true
ports:
- "80:80"
volumes:
# 書き込みが必要な場所は、明示的にボリュームとしてマウントします
- app_data:/app/data
- log_data:/var/log/my_app
volumes:
app_data:
log_data:
この設定では、my_app というサービスに対して readOnlyRootFilesystem: true と指定しています。これにより、コンテナのルートファイルシステム全体が読み取り専用になります。
「ちょっと待って!書き込みが必要な場所はどうするの?」
「でも、アプリケーションによっては、データを保存したり、ログを出力したりするために、書き込みが必要な場所があるんじゃない?」と思われた方、鋭いですね! その通りです。
readOnlyRootFilesystem を有効にすると、コンテナのデフォルトでは書き込みができなくなります。そこで、書き込みが必要な特定のディレクトリだけを「書き込み可能な場所」として指定する必要があります。これは、コンテナの「金庫」の中に、「手荷物一時預かり所」や「作業台」のような、一時的に物を置ける場所を別途用意するイメージです。
Docker Compose では、volumes を使うことでこれを実現できます。上記の例では、app_data と log_data というボリュームを定義し、コンテナ内の /app/data や /var/log/my_app ディレクトリにマウントしています。これらのボリュームは、コンテナの外(ホストマシンなど)にデータが保存されるため、コンテナのルートファイルシステムが書き込み禁止でも、これらのディレクトリには書き込みが可能になります。
ボリュームに書き込みを許可する設定(Docker Compose)
# docker-compose.yml (volumes の詳細設定)
version: '3.8'
services:
my_app:
image: my_app_image:latest
readOnlyRootFilesystem: true
ports:
- "80:80"
volumes:
# ここで、書き込みが必要なディレクトリをボリュームとしてマウントします
# Docker は、ボリュームをマウントする際に、それが書き込み可能であることを自動的に扱います
- app_data:/app/data
- log_data:/var/log/my_app
volumes:
app_data: # ここでは特に何も指定しない場合、Docker が管理するボリュームとして扱われ、書き込み可能になります
log_data:
この設定により、コンテナのルートファイルシステムは安全に保ちつつ、必要な場所への書き込みも行うことができます。
Kubernetes での設定例
Kubernetes を利用している場合も、同様の設定が可能です。Pod のセキュリティコンテキストで readOnlyRootFilesystem を指定します。
# pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-secure-pod
spec:
containers:
- name: my-app-container
image: my_app_image:latest
securityContext:
# ここで readOnlyRootFilesystem を true に設定します
readOnlyRootFilesystem: true
volumeMounts:
# 書き込みが必要な場所は、ボリュームとしてマウントします
- name: app-data-volume
mountPath: /app/data
- name: log-volume
mountPath: /var/log/my_app
volumes:
- name: app-data-volume
emptyDir: {} # 一時的な書き込み領域として利用
- name: log-volume
emptyDir: {} # 一時的な書き込み領域として利用
この例では、securityContext.readOnlyRootFilesystem を true に設定しています。また、書き込みが必要な /app/data や /var/log/my_app は、emptyDir ボリュームとしてマウントしています。emptyDir は、Pod のライフサイクル中に一時的な書き込み領域として機能します。
【重要】allowPrivilegeEscalation についても触れておこう
もう一つ、セキュリティを高めるために知っておきたいのが allowPrivilegeEscalation という設定です。これは、コンテナ内のプロセスが、本来持っている権限よりも高い権限を取得することを許可するかどうかを制御します。
例えるなら、「部屋の中で、勝手にドアをこじ開けるための道具を使うことを許可するかどうか」 というイメージです。
通常、readOnlyRootFilesystem を有効にする場合、権限昇格(Privilege Escalation)も無効にすることが推奨されます。これにより、万が一コンテナ内に侵入されても、攻撃者がさらに深い権限を奪取するのを防ぐことができます。
Kubernetes の securityContext で設定する場合は、以下のようになります。
# pod.yaml (allowPrivilegeEscalation を追加)
apiVersion: v1
kind: Pod
metadata:
name: my-secure-pod
spec:
containers:
- name: my-app-container
image: my_app_image:latest
securityContext:
readOnlyRootFilesystem: true
# 権限昇格を禁止します
allowPrivilegeEscalation: false
volumeMounts:
- name: app-data-volume
mountPath: /app/data
- name: log-volume
mountPath: /var/log/my_app
volumes:
- name: app-data-volume
emptyDir: {}
- name: log-volume
emptyDir: {}
Docker Compose では、security_opt を使って設定することが一般的です。
# docker-compose.yml (allowPrivilegeEscalation を追加)
version: '3.8'
services:
my_app:
image: my_app_image:latest
readOnlyRootFilesystem: true
ports:
- "80:80"
# 権限昇格を禁止するための設定
security_opt:
- no-new-privileges:true
volumes:
- app_data:/app/data
- log_data:/var/log/my_app
volumes:
app_data:
log_data:
まとめ:コンテナセキュリティの第一歩
readOnlyRootFilesystem の設定は、コンテナセキュリティにおける非常に基本的かつ効果的な対策の一つです。まるで、大切なものを「金庫」に入れるように、コンテナのルートファイルシステムを保護することで、攻撃者がマルウェアを仕掛けたり、システムを乗っ取ったりするリスクを大幅に減らすことができます。
もちろん、これだけで万全というわけではありません。しかし、この設定を理解し、適切に実装することは、コンテナセキュリティの強固な土台を築くための、まさに「第一歩」と言えるでしょう。
今回ご紹介した設定を、ぜひ皆さんの開発やインフラ構築に活かしてみてください。セキュリティ対策は、一歩ずつ、着実に進めていくことが大切ですよね!
これからも、皆さんのセキュリティ対策のお役に立てるような情報を発信していきますので、どうぞお楽しみに!
コメント