ブログ

信頼の誤謬:マルチテナントDocker設定がデータを漏洩させた方法(そしてその修正方法)

あるチームの甘いDocker設定がテナント間データ漏洩を引き起こし、それを防いだ層状の分離戦略をご紹介します。

概要

Dockerコンテナはデフォルトでは分離されていません。ホストカーネルを共有しており、意図的な設定がないとテナント同士が干渉する可能性があります。この記事では、マルチテナントホスティングプロバイダーが、共有ネットワークと弱いセキュリティデフォルトにより、クライアントコンテナが互いのデータベースにアクセスできることを発見した実際のシナリオを取り上げます。この侵害を修正するための段階的な変更(テナントごとのユーザー定義ネットワーク、非ルートユーザー、機能の削除、読み取り専用ファイルシステム、seccompプロファイル)を示します。一般的な前提として、コンテナは本質的に強力な分離を提供すると考えられていますが、VMが依然としてより強固な境界を提供する理由と、ハイブリッドアプローチを検討すべきタイミングについて説明することで、その前提に挑戦します。結論として、分離は単一のチェックボックスではなく、層状の取り組みであることを強調します。

インシデント:コンテナが語りすぎるとき

あなたは単一ホスト上にDockerをセットアップして、複数のクライアントウェブサイトを実行しています。各クライアントには独自のコンテナがあり、すっきりと分離された環境ですよね?私たちもそう思っていました。定期的なセキュリティ監査で、クライアントAのコンテナが同じホスト上のクライアントBのコンテナのMySQLソケットを読み取っていることが判明するまでは。彼らはデフォルトの bridge ネットワークを共有していました。さらに悪いことに、コンテナはルートとして実行されていたため、攻撃者が1つを侵害すれば、ホストのDockerソケットや別のコンテナのファイルシステムを改ざんできました。この侵害は高度なエクスプロイトではなく、基本的な設定ミスでした。データが漏洩し、信頼は失われました。

この障害シナリオは珍しくありません。多くのチームは、Dockerの名前空間とcgroupが自動的にテナントを隔離すると想定していますが、デフォルトで多くの脱出ハッチが開いたままになっていることを過小評価しています。デフォルトのブリッジネットワークは、コンテナ間のネットワーク分離を提供しません。ルートとして実行すると、コンテナに必要以上の権限が与えられます。また、明示的なリソース制限がないと、1つの騒がしい隣人が他のCPUやメモリを枯渇させる可能性があります。

ステップ1: 単一ネットワークの共有をやめる

最初の修正は、各テナントに独自のユーザー定義Dockerネットワークを割り当てることでした。これにより、明示的に接続しない限り、コンテナ同士が到達できなくなります。各テナントに対して専用ネットワークを立ち上げ、そのテナントのアプリケーションコンテナを接続するスクリプトを作成しました。データベースコンテナは同じテナントネットワークに存在しますが、テナント内部通信用の内部ネットワークも追加しました。テナント間の覗き見はもうありません。

また、データベースを分離するために、同じテナントネットワーク上の別々のコンテナで実行し、別々のデータボリュームを使用しました。これにより、攻撃者がアプリコンテナに侵入しても、別のテナントのデータベーストラフィックを傍受できないようにしました。

ネットワーク分離戦略の詳細については、マルチテナントホスティングのための実践的なDocker分離セキュリティチェックリスト を参照してください。

ステップ2: 不要な特権を削除する

デフォルトでは、Dockerコンテナは限られたLinux機能セットで実行されますが、それでもほとんどのアプリケーションが必要とする以上に多くの機能を持っています。私たちのコンテナはルートとして実行されており、内部のプロセスがファイルシステムのマウントやカーネルパラメータの変更などのアクションを実行できました。コンテナ内でアプリケーションを非ルートユーザーとして実行するように切り替え(Dockerfileの USER ディレクティブを使用)、絶対に必要なものを除くすべての機能を削除しました。典型的なウェブアプリの場合、必要なのは NET_BIND_SERVICE(1024未満のポートにバインドするため)と CHOWN(ディレクトリに書き込むため)だけかもしれません。また、--security-opt no-new-privileges を追加して、特権昇格を防止しました。

このステップだけで、多くの一般的なコンテナエスケープベクターが排除されました。ウェブサーバーを侵害した攻撃者は、パッケージのインストール、システムバイナリの変更、ホストのDockerソケットへのアクセスができなくなります。プロセスが CAP_SYS_ADMINCAP_DAC_OVERRIDE 機能を持っていないからです。

ステップ3: ファイルシステムをロックダウンする

書き込み可能なファイルシステムは一般的な攻撃対象です。すべてのコンテナのルートファイルシステムを読み取り専用(--read-only)にし、書き込みアクセスが必要なディレクトリ(/tmp やアプリケーションのキャッシュディレクトリなど)には一時ファイルシステム(tmpfs)をマウントしました。これにより、攻撃者がアプリケーションコードを変更したり、悪意のあるバイナリを永続化したりするのを防ぎます。

さらに、Dockerの --mount オプションを使用して、Dockerソケットなどの機密ディレクトリを絶対に必要な場合にのみバインドマウントし、本番コンテナでは決してマウントしないようにしました。原則:コンテナがパスに書き込む必要がない場合は、読み取り専用にする。

ステップ4: Seccomp および AppArmor プロファイルを適用する

デフォルトのseccompプロファイルはすでに多くの危険なシステムコールをブロックしていますが、アプリケーションが実際に必要とするシステムコールのみをホワイトリスト化するためにさらにカスタマイズしました。これはアプリケーションのプロファイリングが必要なため、トレードオフがあります。より簡単なアプローチは、Dockerのデフォルトseccompプロファイルを使用し、より厳格なルールが必要な場合に --security-opt seccomp=path/to/profile.json を追加することです。同様に、AppArmorプロファイルはコンテナプロセスを特定のファイルパスと機能に制限できます。AppArmorを有効にし、アプリケーションのデータディレクトリのみにアクセスを制限するカスタムプロファイルを使用しました。

これらの強化手順の包括的なガイドについては、マルチテナントホスティングのためのDockerコンテナ強化:ステップバイステップ分離ガイド を参照してください。

対抗意見:時にはVMが必要

どんなに強化しても、コンテナはホストのカーネルを共有します。カーネルの脆弱性はすべての分離を一度に破壊する可能性があります。そのため、セキュリティ重視の多くのプラットフォームは、軽量VM内でコンテナを実行しています。各テナントは独自のカーネルを持ちます。これによりオーバーヘッドは増加しますが、コンテナだけでは提供できないハードウェアレベルの境界が提供されます。テナントがクレジットカードデータや医療記録を扱う場合、ハイブリッドアプローチ(VM内のコンテナ)が適切な選択かもしれません。コンテナ分離が脅威モデルに十分であると想定せず、データの機密性と規制要件を評価してください。

分離レベルの詳細な比較については、マルチテナントDockerアーキテクチャの設計:適切な分離レベルの選択 をお読みください。

結論:分離はスイッチではなくスタック

修正は単一の変更ではなく、ネットワーク分離、制限された特権、読み取り専用ファイルシステム、システムコールフィルタリングの層状化でした。それでもなお、共有カーネルコンテナでは完全な分離は不可能であると認識していました。最もセキュリティの高いテナントについては、専用ホストに移行しました。教訓:デフォルトを信頼するな。すでに侵害が発生したかのようにDocker設定を監査せよ。ロックダウンは漏洩後ではなく、その前に行うべきです。

Sources (5)