ブログ

Dockerにおける真のマルチテナント分離の実現

Dockerの共有カーネルモデルはマルチテナント環境にリスクをもたらします。このガイドでは、ユーザ名前空間、seccomp、AppArmor、サンドボックスツール、およびオーケストレーションのベストプラクティスを使用して分離を強化する具体的な手順を提供します。

概要

Dockerコンテナはホストカーネルを共有するため、テナント同士が信頼しないマルチテナント環境ではセキュリティ上の懸念があります。この記事では、デフォルトのDocker設定における分離のギャップを説明し、Linux名前空間、cgroups、ユーザ名前空間、seccomp、AppArmor、ハードウェア仮想化を使用して分離を強化する具体的な手順を提供します。テナントごとのDockerデーモンの設定方法、gVisorやFirecrackerなどのサンドボックスツールを使用したより強力な分離、およびKubernetesを使用したマルチテナンシーのオーケストレーションについて学びます。また、追加の分離レイヤーとしてKVMベースの仮想化を提供する適切なインフラストラクチャプロバイダーの選択についても説明します。最後に、Dockerで安全なマルチテナントワークロードを実行するための設計図を提供します。

単一のDockerホストで複数のテナントをホストする場合、Linux名前空間とcgroupsに基づくデフォルトのコンテナ分離では不十分なことがよくあります。あるテナントのコンテナエスケープがホスト全体と他のすべてのコンテナを危険にさらす可能性があります。この問題は、共有ホスティング、SaaSプラットフォーム、信頼できないコードが自社コードと一緒に実行されるシナリオで特に深刻です。幸いなことに、複数の分離技術を積み重ねて強化されたマルチテナント環境を構築できます。このガイドでは、ユーザ名前空間のような簡単な対策から、サンドボックスランタイムやインフラストラクチャの選択といった高度な対策まで、6つの実践的な手順を説明します。

Dockerのデフォルト分離を理解する

DockerはLinux名前空間を使用してプロセス、ネットワーク、ファイルシステムなどのリソースを分離します。CgroupsはCPU、メモリ、I/Oを制限します。しかし、これらは単一のカーネルを共有しており、カーネルの脆弱性がすべてのコンテナに影響を与える可能性があります。真のマルチテナンシー、特に信頼できないテナントの場合は、多層防御が必要です。マルチテナントDockerアーキテクチャの設計:適切な分離レベルの選択で説明されているように、分離レベルは弱い(名前空間のみ)ものから強い(ハードウェア仮想化)ものまであります。最も弱いものから構築しましょう。

ステップ1:ユーザ名前空間を有効にする

デフォルトでは、コンテナ内のrootはホストのrootにマッピングされます。コンテナの脱出により、ホストへの完全なアクセスが可能になります。ユーザ名前空間は、コンテナのrootを外部の非rootユーザーに再マッピングします。dockerd --userns-remap=defaultでグローバルに有効にするか、--userns=hostでコンテナごとに有効にします。この簡単な手順で、多くの権限昇格攻撃を排除できます。アプリケーションをテストしてください。ホストレベルの権限が必要なもの(例:ファイルシステムのマウント)は動作しなくなる可能性があります。DrupalやWordPressサイトでは通常安全です。

ステップ2:SeccompとAppArmorプロファイルを適用する

Seccompはコンテナが実行できるシステムコールを制限します。Dockerには、mountrebootなどの危険なシステムコールをブロックするデフォルトのseccompプロファイルが付属しています。マルチテナントの場合はさらに厳しくし、エスケープツールが使用する一般的でないシステムコールをブロックします。同様に、AppArmorはコンテナプロセスを制限できます。カーネルインターフェースへの書き込みアクセスを拒否し、ファイルパスを制限するカスタムAppArmorプロファイルを作成します。どちらも--security-optフラグで設定します。これらを組み合わせて多層防御にします。

ステップ3:テナントごとのDockerデーモンを使用する

すべてのテナントに単一のDockerデーモンを実行するのはリスクがあります。コンテナエスケープがデーモンソケットにアクセスする可能性があります。Docker-in-Docker(DinD)またはリモートデーモンエンドポイントを使用して、テナントごとにデーモンを分離します。たとえば、--privilegedを指定してコンテナ内でDockerデーモンを起動します(ただし、これにより分離が弱まります)。より良いアプローチは、別々のVMで別々のデーモンを実行するか、ユーザ名前空間とともにDockerの実験的な--group機能を使用することです。オーケストレーションには、コンテナエスケープからの防御:マルチテナントホスティングのためのDocker分離の実践ガイドでカバーされているように、Kubernetes名前空間ベースの分離がより実用的です。

ステップ4:サンドボックスランタイムを検討する

Linuxカーネル自体が信頼できない場合は、軽量VMレイヤーを追加するサンドボックスランタイムを使用します。gVisor(runsc)はシステムコールをインターセプトし、独自のカーネルを実装します。一方、Firecrackerはハードウェア仮想化を備えたマイクロVMを使用します。どちらもcontainerdランタイムを介してDockerと統合します。たとえば、Dockerデーモン設定に"runtimes": {"runsc": {}}を追加し、--runtime=runscでコンテナを実行します。パフォーマンスのオーバーヘッドは5〜15%ですが、分離ははるかに強力です。セキュリティ要件の高いマルチテナントセットアップに最適です。

ステップ5:Kubernetesとセキュリティポリシーでオーケストレーションする

Kubernetesは、名前空間、Podセキュリティ基準、NetworkPolicyを通じてネイティブのマルチテナンシーを提供します。テナントごとに名前空間を定義し、リソースクォータを設定し、制限付きPodセキュリティコンテキスト(すべての機能を削除、読み取り専用ルートファイルシステム)を適用します。OPA/Gatekeeperなどのアドミッションコントローラーは、誤設定をブロックできます。多くのテナントを管理する場合、Kubernetesは分離の適用を自動化します。プロダクション規模のオーケストレーションについては、Docker Composeを超えて:プロダクション対応のコンテナ化アプリケーションのオーケストレーションを参照してください。

ステップ6:適切なホスティングプロバイダーを選択する

インフラストラクチャプロバイダーのハイパーバイザーは重要です。共有ホスティング(OpenVZ)上のDockerは分離が弱く、あるテナントが他のプロセスを見ることができます。KVMやVMwareを使用するプロバイダーを優先します。これらはハードウェアレベルの分離を提供します。DigitalOcean、Kamatera、AWSなどのプロバイダーは、専用リソースを備えたKVMベースのVPSを提供しています。ベアメタルの場合は、ネストされたコンテナのためにBIOSレベルの仮想化が有効になっていることを確認します。ハイパーバイザーレベルでテナントを分離するプロバイダーは、コンテナの分離を補完します。安全で効率的なWebホスティングのためのDocker分離の習得で詳述されているように、ホストOSも攻撃面を最小限に抑えて強化する必要があります。

注意点とトレードオフ

追加のレイヤーごとに複雑さとパフォーマンスコストが増加します。ユーザ名前空間はホストマウントボリュームを壊す可能性があります。Seccompプロファイルはアプリケーションごとに調整が必要です。gVisorのようなサンドボックスランタイムはすべてのシステムコールをサポートしているわけではなく、アプリが動作しない可能性があります。テナントごとのDockerデーモンはメモリオーバーヘッドを増加させます。脅威モデルに一致する分離レベルを選択してください。信頼できるテナントの場合はデフォルトの名前空間で十分ですが、パブリックSaaSの場合はランタイムサンドボックスとKubernetesポリシーに投資してください。本番環境に移行する前に徹底的にテストしてください。

結論

Dockerにおける真のマルチテナント分離は、複数のカーネル機能、ランタイムサンドボックス、オーケストレーション制御を階層化することで実現可能です。ユーザ名前空間とseccompから始め、テナントごとのデーモンまたはサンドボックスランタイムに進みます。大規模な場合は、Kubernetesがポリシーベースの分離を提供します。常に評判の良いプロバイダーからのハイパーバイザーレベルの分離されたホストと組み合わせてください。単一の手法は完全ではありませんが、それらを組み合わせることで堅牢な防御が構築されます。テナントからもセキュリティ監査からも感謝されるでしょう。

Sources (5)