【入門編】 AppArmor/SELinuxによるコンテナの強制アクセス制御 – インフラ・ネットワーク & クラウドセキュリティ防御ガイド

皆さん、こんにちは!セキュリティバイブル主筆ライターのサイバー・アイです。ITの最前線で日夜奮闘されている皆さん、本当にご苦労様です。

最近、コンテナ技術の進化は目覚ましいものがありますよね。開発もデプロイも、あっという間にできてしまう。まさに現代の魔法のようなツールです。ですが、その便利さの裏には、攻撃者が狙う「盲点」が隠されていることも少なくありません。

「え、コンテナって隔離されてるから安全なんじゃないの?」

そう思われた方もいらっしゃるかもしれませんね。今日は、その「見えない壁」の裏側に潜むリスクと、それを鉄壁の要塞に変えるための最強の盾、AppArmor と SELinux について、泥棒対策や身近な防犯に例えながら、とことん優しく紐解いていきましょう!

—

泥棒も思わず唸る!コンテナの「見えない壁」を強化するAppArmor/SELinuxのすごい力

コンテナセキュリティの「盲点」:侵入されたらどうなる?

コンテナ、便利ですよね。DockerやKubernetesを使えば、アプリケーションとその実行環境をまとめてポータブルなパッケージにできます。まるで、自分の部屋に好きな家具や家電を自由に配置できるような感覚です。

そして、コンテナは「隔離」されている、というのが大きな特徴です。イメージしてみてください。あなたの住んでいるマンションの各部屋がコンテナだとします。それぞれの部屋は壁で仕切られ、隣の部屋の住人が勝手にあなたの部屋に入ってくることはありません。これがある種の「隔離」です。

しかし、もし泥棒があなたの部屋の鍵をこじ開けて侵入してしまったら、どうなるでしょう?

従来のセキュリティ対策、例えばファイルやディレクトリに対する「読み取り」「書き込み」「実行」といったパーミッション(権限)は、マンションの「ドアの鍵」のようなものです。鍵を持っている人(特定のユーザーやグループ)だけが部屋に入れます。しかし、一度泥棒が鍵を破って侵入してしまえば、部屋の中では「何でもできてしまう」という状況になりがちです。

泥棒は、部屋の中にある大切なもの(データ)を盗んだり、勝手に物を配置したり(不正なプログラムの実行)、最悪の場合、バルコニー伝いに隣の部屋へ侵入したり(横展開)、さらにはマンションの管理室に忍び込んで全体のシステムを乗っ取ろうとするかもしれません(ホストエスケープ)。

コンテナの場合も同じです。もし、脆弱性などを突かれてコンテナに侵入されてしまうと、そのコンテナが持つ権限を使って、ホストOS(マンション全体)へのアクセスを試みたり、他のコンテナ(隣の部屋)へ横展開したりするリスクがあるのです。

「コンテナは隔離されているから大丈夫」という考えは、残念ながらこの「部屋の中での無法地帯」という盲点を見落としがちなんですね。

最強のセキュリティガード「強制アクセス制御(MAC)」登場!

そこで登場するのが、今日の本題である 強制アクセス制御(MAC: Mandatory Access Control) です。これは、従来の「ドアの鍵」だけでは防げなかった「部屋の中で何でもできる」という状況を根本から変える、非常に強力な防犯システムだと考えてください。

MACは、例えるなら「部屋の中での行動規範リスト」や「身分証明書と物品タグの組み合わせ」のようなものです。

  • 泥棒がドアの鍵を破って部屋に侵入したとしても、
  • 事前に設定された「行動規範」や「身分証明書」によって、
  • 「この部屋の住人(コンテナプロセス)は、冷蔵庫(特定のファイル)は開けられるけど、金庫(システムファイル)は開けられない」
  • 「この部屋の住人は、窓から外を見るのはOKだけど、外に物を投げ捨てる(外部ネットワークへの接続)のはNG」

といったように、たとえ侵入者であっても、その行動を細かく制限してしまうのです。これがMACの真髄。泥棒は「え、鍵は開けたのに、なんでこんなことまでできないんだ!?」と頭を抱えること間違いなしです。

コンテナの強制アクセス制御において、この「行動規範リスト」や「身分証明書」の役割を果たすのが、主に AppArmor と SELinux の2つのシステムになります。

OSによって得意なガードマンが違う、と考えると分かりやすいでしょう。

  • AppArmor:UbuntuやDebian系のOSでよく使われます。ルールがシンプルで分かりやすく、特定のプロセスに対する「行動規範リスト」を作るのが得意なガードマンです。
  • SELinux:Red Hat系のOS(RHEL, CentOS, Fedoraなど)で広く使われています。より厳密で強力ですが、その分設定が複雑になりがちな「身分証明書と物品タグの組み合わせ」で管理する、ベテランガードマンです。

今回は、より初心者の方にもとっつきやすい AppArmor を例に、実際にコンテナを鉄壁に守る方法を見ていきましょう!SELinuxについても少しだけ触れますのでご安心くださいね。

AppArmorでコンテナを鉄壁に守ろう!

AppArmorは、特定のプログラム(コンテナ内で動くプロセス)がアクセスできるファイルやディレクトリ、ネットワーク通信などを細かく定義した「プロファイル」に基づいて、そのプログラムの行動を制限します。

この「プロファイル」は、泥棒が侵入したとしても、その行動を制限するための「許可証」や「行動リスト」のようなものです。

AppArmorのプロファイルには、大きく分けて3つのモードがあります。

  • enforce (強制モード): これは「監視カメラが不審な行動を検知したら、即座にブロックし、警告音を鳴らす」モードです。設定されたルールに反する行動は、問答無用で停止されます。本番環境ではこのモードが基本です。
  • complain (苦情モード): 「監視カメラは不審な行動を検知したら、警告音は鳴らさずに、こっそり記録だけしておく」モードです。実際にブロックはしませんが、ログに記録を残してくれます。プロファイルをテストしたり、問題がないか確認したりする際に便利です。
  • disabled (無効モード): 「監視カメラの電源を切っている」モードです。AppArmorによる監視や制限は一切行われません。

まずは、complain モードでプロファイルを作成し、問題がないか確認しながら enforce モードへ移行していくのが、賢い運用方法ですよ。

DockerとAppArmorで実践!プロファイルの作成と適用

それでは、実際にDockerコンテナにAppArmorプロファイルを適用してみましょう。今回は、Nginxコンテナを例に、必要最低限のアクセスだけを許可するプロファイルを作成します。

1. AppArmorプロファイルの作成

まず、プロファイルファイルを /etc/apparmor.d/ ディレクトリに作成します。ファイル名は任意ですが、慣例として docker- をプレフィックスに付けることが多いです。今回は docker-nginx-profile としましょう。

# プロファイルファイルを新規作成します
sudo vim /etc/apparmor.d/docker-nginx-profile

そして、以下の内容を記述してください。日本語で丁寧なコメントを入れていますので、ぜひ参考にしながら読み解いてみてくださいね。

#include <tunables/global>

# プロファイルの名前を定義します。
# Dockerで使用する場合、"docker-" プレフィックスを付けるのが一般的です。
# ここで定義した名前が、Docker実行時に指定するプロファイル名になります。
profile docker-nginx-profile flags=(attach_disconnected, complain) {
  # グローバルなチューニング設定を読み込みます。
  # 一般的なシステムパスなどが含まれます。
  #include <abstractions/base>
  #include <abstractions/user-tmp>

  # ネットワーク通信に関するルールを定義します。
  # deny (拒否), allow (許可)
  # unconfined (無制限)
  #
  # この例では、特定のポートへのTCP接続のみを許可し、それ以外は拒否します。
  # Dockerコンテナがホストのネットワークにアクセスする際のルールになります。
  network tcp {
    # NginxがWebサーバーとして利用する一般的なポート80と443への送信を許可します。
    # これはコンテナから外部への接続を指します。
    # コンテナ内部でlistenするポートはDockerのポートマッピングで制御されます。
    # 例えば、Nginxが外部のデータベースに接続する場合など。
    # 実際には必要に応じてポートを追加してください。
    port 80 tcp send,
    port 443 tcp send,
    # それ以外の全てのTCP接続を拒否します。
    # これにより、コンテナから不審な外部アクセスが行われるのを防ぎます。
    deny tcp,
  }
  # UDP通信も同様に全て拒否します。必要であれば許可ルールを追加してください。
  deny network udp,
  # RAWソケット通信も全て拒否します。
  deny network raw,

  # コンテナ内で実行されるプロセスに対するファイルアクセスルールを定義します。
  # r (読み取り), w (書き込み), x (実行), a (追記), l (ロック), m (mmap実行)
  #
  # この設定により、コンテナ内のプロセスが、ホストOSのファイルシステムに
  # 不必要にアクセスするのを防ぎます。
  #
  # 通常のNginxコンテナが必要とする最低限のファイルアクセス権限を設定します。
  # /usr/sbin/nginx への実行権限
  /usr/sbin/nginx cx, # 'x' は実行可能ファイル、'c' はその子プロセスも継承
  
  # Nginxの設定ファイルやログファイルへのアクセス権限
  /etc/nginx/** r,   # Nginx設定ファイルディレクトリの読み取り
  /var/log/nginx/** rw, # Nginxログファイルの読み書き

  # Nginxが提供する静的コンテンツのルートディレクトリ(例: /usr/share/nginx/html)
  # Dockerfileで設定したDocumentRootに合わせて調整してください。
  /usr/share/nginx/html/** r,

  # その他、Nginxが一時ファイルやキャッシュで利用する可能性のあるディレクトリ
  /var/cache/nginx/** rw,
  /var/lib/nginx/** rw,

  # 重要なシステムディレクトリへのアクセスは基本的に拒否します。
  # 明示的に許可しない限り、デフォルトで拒否されるのがAppArmorの考え方ですが、
  # より安全を期すために明示的に拒否するルールを記述することもあります。
  # ただし、デフォルトでdeny allが効いているため、通常は許可するものを記述します。

  # 以下のルールは、特定のパスへのアクセスをより細かく制御する場合に記述します。
  # 例: /bin/bash をコンテナ内で実行させたくない場合など。
  # deny /bin/bash x,

  # 上記で定義されていない全てのファイルシステムアクセスを拒否します。
  # これが非常に重要で、明示的に許可されたもの以外は全てアクセスできなくなります。
  # AppArmorプロファイルの終わりに `deny` がない場合、暗黙的に許可される振る舞いを
  # する場合がありますが、`owner` キーワードを付けているプロファイルや
  # Dockerと連携する場合は、通常、定義されていないものは拒否されます。
  # より確実にするには、プロファイルの最後に `deny file,` などと明示的に記述することも検討します。
  # ただし、DockerのAppArmorプロファイルは、デフォルトで"deny everything unless specified"の
  # ポリシーで動作することが多いです。

  # このプロファイルは最初は complain モードでロードされるため、ブロックはされませんが、
  # ログに記録されます。
  # 動作確認後、`flags=(attach_disconnected)` のみを残し、`complain` を削除して
  # `enforce` モードへ移行します。
}

このプロファイルは、Nginxコンテナが必要とする最小限のファイルアクセスとネットワークアクセスだけを許可するように設計されています。これ以外の操作は基本的にブロックされることになります。

2. プロファイルのロードと確認

作成したプロファイルをAppArmorにロードします。最初は complain モードでロードされるように、プロファイル内の flags=(attach_disconnected, complain) を確認しておきましょう。

# プロファイルをAppArmorにロードします
sudo apparmor_parser -q /etc/apparmor.d/docker-nginx-profile

# ロードされたプロファイルの一覧を確認します
# complainモードでロードされているか確認してください
sudo apparmor_status

apparmor_status コマンドの出力で、docker-nginx-profile が complain モードで表示されていればOKです。

3. Dockerコンテナへのプロファイルの適用

いよいよ、コンテナにプロファイルを適用して起動します。docker run コマンドの --security-opt apparmor=<プロファイル名> オプションを使います。

# AppArmorプロファイルを適用してNginxコンテナを起動します
# -p 8080:80 でホストの8080ポートをコンテナの80ポートにマッピング
# --name my-nginx でコンテナに名前を付けます
# --security-opt apparmor=docker-nginx-profile で作成したプロファイルを適用
docker run -d -p 8080:80 --name my-nginx --security-opt apparmor=docker-nginx-profile nginx:latest

# コンテナが起動していることを確認します
docker ps

これでNginxコンテナが、あなたが作成したAppArmorプロファイルの監視下で動作し始めました。

4. 動作確認とログの監視

Webブラウザで http://localhost:8080 にアクセスし、Nginxのデフォルトページが表示されるか確認してください。これは、ポート80へのアクセスが許可されているため問題なく動作するはずです。

では、意図的に制限に引っかかる操作を試してみましょう。例えば、コンテナ内から本来アクセスが許可されていないホストOSのファイルにアクセスしようとします。

# コンテナ内部に入ります
docker exec -it my-nginx bash

# コンテナ内部からホストOSの /etc/shadow ファイルにアクセスを試みます
# AppArmorプロファイルで明示的に許可していないため、通常は拒否されます
cat /etc/shadow

このコマンドは、コンテナが /etc/shadow にアクセスしようとするため、AppArmorによってブロックされるか、またはログに記録されるはずです(complain モードの場合は記録のみ)。

ログを確認するには、dmesg コマンドやシステムログ(/var/log/syslog や /var/log/audit/audit.log など、OSによって異なります)をチェックします。

# dmesgでカーネルログを確認します(AppArmorの拒否ログが含まれることがあります)
sudo dmesg | grep "apparmor"

# syslogを確認します(Ubuntu/Debian系の場合)
sudo tail -f /var/log/syslog | grep "apparmor"

もし complain モードであれば、audit メッセージとして「アクセスが拒否された」という内容が記録されているはずです。これが確認できたら、プロファイルが正しく機能している証拠です。

全ての動作が問題ないことを確認できたら、プロファイルから complain フラグを削除し、再ロードすることで enforce モードに切り替えて本番運用に移りましょう。

# プロファイルから complain を削除します
profile docker-nginx-profile flags=(attach_disconnected) {
  # ... (中略) ...
}
# プロファイルを再ロードします
sudo apparmor_parser -r /etc/apparmor.d/docker-nginx-profile

# AppArmorのステータスを確認し、enforceモードになっていることを確認します
sudo apparmor_status

これで、あなたのNginxコンテナはAppArmorという強力なガードマンによって、必要最小限の行動しか許されない、まさに鉄壁の要塞と化したわけです。泥棒もこれにはお手上げでしょう!

SELinuxもちょっとだけ知っておこう!

Red Hat系のOSを使っている方は、SELinux (Security-Enhanced Linux) の方が馴染み深いかもしれませんね。SELinuxもAppArmorと同じ強制アクセス制御の仕組みですが、そのアプローチは少し異なります。

SELinuxは、ファイル、プロセス、ポートなど、システム上のあらゆる「モノ」に「コンテキスト」(タグや身分証明書のようなもの)を付与し、そのコンテキスト同士の「やり取り」を許可するかどうかでアクセスを制御します。

例えるなら、

  • すべてのモノ(ファイル、プロセス)に「これは『Webサーバーのデータ』です」「これは『Webサーバーのプロセス』です」といったタグを貼ります。
  • そして、「『Webサーバーのプロセス』は『Webサーバーのデータ』にアクセスできるが、『データベースのデータ』にはアクセスできない」というルールを定義するのです。

この「タグ付け」と「タグ間の関係」で全てを制御するため、非常に強力で細かく設定できる反面、設定が複雑になりがちです。

DockerでSELinuxを利用する場合も、AppArmorと同様に docker run コマンドにオプションを追加します。

# SELinuxを有効にしてコンテナを起動する場合
# container_t は、Dockerコンテナに与えられる一般的なSELinuxタイプです。
docker run -d -p 8080:80 --name my-nginx-selinux --security-opt label=type:container_t nginx:latest

これにより、コンテナは container_t というSELinuxコンテキストで実行され、SELinuxのポリシーによってその行動が制限されます。SELinuxのポリシーはOSにプリインストールされているものを使うのが一般的ですが、より厳密な制御が必要な場合は、専用のポリシーモジュールを作成することもあります。

AppArmorもSELinuxも、アプローチは違えど、狙いは同じ。「万が一侵入されても、被害を最小限に食い止める」ための、最後の砦となる強力なセキュリティメカニズムなんですね。

まとめ:一歩ずつ、セキュアなコンテナ運用へ!

いかがでしたでしょうか?コンテナの「見えない壁」の裏側に潜むリスクと、それを強固な「鉄壁」に変えるAppArmorやSELinuxの仕組み、そしてその具体的な適用方法について、泥棒対策に例えながら解説してきました。

「コンテナは隔離されているから安全」という神話は、残念ながら通用しません。攻撃者は常に盲点を狙い、セキュリティが手薄な部分から侵入しようとします。しかし、AppArmorやSELinuxのような強制アクセス制御を導入することで、たとえコンテナに侵入されてしまったとしても、そこからシステム全体への被害拡大を防ぐ「最後の砦」を築くことができます。

もちろん、完璧なシステムなどこの世には存在しません。しかし、一つ一つの対策を地道に、そして着実に積み重ねていくことが、セキュアなシステムを構築する上で何よりも重要です。今日学んだ強制アクセス制御は、その中でも特に強力な一手となるでしょう。

最初は少し難しく感じるかもしれませんが、一歩ずつ対策を学んでいきましょう!今日の内容が、皆さんのコンテナセキュリティ強化の一助となれば幸いです。

これからも、皆さんのシステムをサイバーの脅威から守るための知見を、このブログでお伝えしていきますので、どうぞご期待くださいね!それでは、また次回の記事でお会いしましょう!

コメント

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