コンテナ分離のベストプラクティス
Maryann Agofure
2022年8月29日
0 分で読めますコンテナは、アプリケーションを予測可能かつ再現可能な方法で実行できる、標準化されたソフトウェアパッケージ形式です。コンテナ分離は、コンテナ化されたアプリケーションがもたらす主なメリットの1つです。コンテナを利用すると、ソフトウェアを実行環境から分離できるため、開発環境とステージング環境の一貫性と信頼性が高まります。
Dockerコンテナをご存じ、あるいは実際に使っている方も多いでしょう。Dockerコンテナは、コントロールグループ(一般にcgroupsと略されます)、セキュアコンピューティングモード(seccomp)フィルター、カーネル名前空間といったLinuxの機能を活用して分離を実現します。ほかにも、gVisorのようなサンドボックス型コンテナや、AWS Firecrackerのような仮想化コンテナがあります。
コンテナは設計上分離されていますが、安全性を確保するには、コンテナを効果的に分離するためのベストプラクティスを実践する必要があります。この記事では、コンテナ分離がもたらす影響を解説し、Linuxコンテナ、サンドボックス型コンテナ、仮想化コンテナのセキュリティと運用方法に関するベストプラクティスを紹介します。
コンテナ分離とは何か、どのように機能するのか
その名のとおり、コンテナ分離とは、コンテナ化されたアプリケーションの実行環境をホストOSやホスト上で実行中のほかのプロセスから切り離すことです。
分離には、ファイルシステム、ネットワーク、システムコール、CPUやメモリなどのリソース使用量の分離など、さまざまな形があります。
コンテナ分離の仕組みの技術的な詳細は、使用するコンテナによって異なります。見ていくとおり、選択肢はいくつかあります。
コンテナ分離のさまざまなアプローチ
前述のとおり、この記事では、DockerのようなLinuxコンテナ、gVisorのようなサンドボックス型コンテナ、AWS Firecrackerのような軽量なKVMベースの仮想化という、3種類のコンテナとコンテナ分離の関係を解説します。
コンテナの種類ごとに分離の方法は異なり、隔離するシステムの要素も異なります。コンテナを分離する際には、それぞれの種類に応じたベストプラクティスに従う必要があります。
それぞれの種類のコンテナにおける分離のベストプラクティスを見ていきましょう。
Linuxコンテナ
DockerのようなLinuxコンテナは、cgroups、seccompフィルター、カーネル名前空間を使ってコンテナを分離します。cgroupsを使うと、プロセスグループにリソース使用量の上限を設定できます。たとえば、ディスクI/O、メモリ、ネットワーク、CPU時間、マルチコアシステムの個々のCPUなど、さまざまなリソースの使用量を制限できます。seccompを使うと、プロセスが実行するすべてのシステムコールをフィルタリングできます。このフィルタリングとカーネルの名前空間機能を組み合わせることで、コンテナ内の関数からシステムを分離して見せることができます。
このアプローチは最もシンプルですが、その分、Dockerではコンテナ作成時に、すべてのコンテナ化アプリケーションで名前空間のフラグが正しく設定されていることを確認する必要があります。
Dockerのアプローチは比較的シンプルで、使いやすいのが特長です。しかし、シンプルであるがゆえに、分離の強度はgVisorなどの代替手段ほど高くありません。Dockerでは名前空間の境界を越える一部のシステムコールを許可する必要があるため、seccompフィルターで該当する呼び出しに有効な引数が認められていれば、アプリケーションがホスト上の情報にアクセスできる可能性があります。
幸い、Dockerでコンテナ分離の効果を最大限に高めるために、実践できるベストプラクティスがいくつかあります。
Dockerfileで専用のユーザーアカウントを指定し、コンテナごとに専用ユーザーを使用します。これにより、あるコンテナが別のコンテナのリソース(ファイルやディレクトリなど)にアクセスしたり、変更したりできないようにします。新しいDockerイメージやrunc構成を作成する際は、可能な限り最小限の権限を持つユーザーをコンテナに割り当てましょう。
前述のLinuxケイパビリティを使って、コンテナの権限を制限します。最小権限の原則に従い、タスクの遂行に必要なものだけをコンテナに付与します。この原則に従えば、コンテナ内のアプリケーションが分離環境の外部で参照または実行できることを制限できます。
コンテナごとに、不要なネットワークデバイスが設定されないようにします。これにより、ホストのネットワークインフラストラクチャ上でコンテナが参照または実行できることを制限できます。
cgroupの制限を使って、CPUシェア、メモリページ、ブロックI/O帯域幅など、各コンテナが利用できるリソースを制限します。
PID数、最大スタックサイズ、コンテナが生成できるスレッドの最大数など、カーネルパラメーターを設定します。これにより、あるコンテナが別のコンテナのPID名前空間を乗っ取ったり、パニックを引き起こしてホストのカーネルをクラッシュさせたりするのを防げます。
これらの対策を組み合わせることで、攻撃者が悪用できる対象を減らし、コンテナの攻撃対象領域を縮小できます。もちろん、最善のセキュリティ対策は、信頼できないコードをコンテナで実行しないことです。しかし、そのためには各コンテナで実行しているものを正確に把握する必要があります。納期に追われる開発チームやDevOpsチームにとって、それを実現するのは現実的でないことがほとんどです。
信頼できないコンテナを実行する必要がある場合、次善の策として、より強固なセキュリティモデルを備えたコンテナランタイムの利用を検討しましょう。
サンドボックス型コンテナ
サンドボックス型コンテナは、通常のLinuxコンテナと同じ分離機能に加えて、さらなる保護レイヤーを提供します。
優れたサンドボックス型コンテナであるgVisorは、コンテナ化アプリケーションとホストカーネルの間に位置する、独自のユーザー空間ミニカーネルを実装しています。コンテナのすべてのシステムコールを傍受し、ホストカーネルに渡す前にポリシーチェックを行います。また、コンテナ化されたワークロードのネットワーク通信をより厳密に制御するため、独自のTCP/IPスタックも実装しています。さらに、コンテナとホストのファイルシステムの間に位置するファイルシステムプロキシも実装しています。こうした多層的なチェックにより、ほとんどのアプリケーションとの互換性を維持しながら、明確なコンテナ境界を設けて攻撃対象領域を縮小できます。また、gVisorはメモリ安全な言語であるGoで記述されているため、LinuxカーネルのようなC言語で書かれたソフトウェアに比べ、バッファーオーバーフローなどの悪用が起こる可能性もはるかに低くなります。
ただし、gVisorのアプローチには欠点もあります。Linuxカーネル向けに設計されたツールの多くを使えないため、gVisor内で実行されるアプリケーションのデバッグが難しい場合があります。また、標準的なLinuxカーネルを使用しないため、gVisor内で実行する一部のワークロードをサポートするには、機能を再実装しなければならないこともあります。
gVisorなどのサンドボックス型コンテナは通常のLinuxコンテナよりも優れていますが、さらに強固な分離を実現するコンテナモデルに切り替えることが、最適なベストプラクティスとなる場合もあります。
軽量仮想マシン
Linuxコンテナやサンドボックス型コンテナとは異なり、AWS Firecrackerのような軽量な仮想マシン(VM)ベースのコンテナは、まったく異なるアプローチを採用しています。KVMやqemuなどのハイパーバイザーを使い、コンテナごとに軽量VM(一般にマイクロVMと呼ばれます)を作成します。この強固な分離によって、ゲストのマイクロVM内の攻撃対象領域を最小限に抑え、容易に制御できます。
この方式では、攻撃者がホスト上の機密情報にアクセスするのは非常に困難になりますが、コンテナがホストとやり取りする手段も限られます。たとえば、マイクロVMで実行するコンテナは、ホストとファイルを直接共有できません。
マイクロVMは、通常のLinuxカーネルがサポートするハードウェアデバイスの一部だけをサポートすればよいため、従来型のVM上で実行するコンテナより攻撃対象領域が小さくなります。
総合的に見ると、同じサーバー上で複数テナントの信頼できないコードを実行する場合など、コンテナ間に非常に強固な分離が必要なら、マイクロVMが最適です。
コンテナ分離の考え方
どの種類のコンテナを使う場合も、コンテナ分離に万能な解決策はありません。そのため、各プロジェクト固有の要件に合わせて、次の要素のバランスを取る必要があります。
パフォーマンス。コンテナとホストOSの間に介在するレイヤーが最も少ないため、Linuxコンテナが最高のパフォーマンスを発揮します。サンドボックス型コンテナは、コンテナとホストOSの間に追加のレイヤーがあるため、測定可能なほど速度が低下します。マイクロVMは、コンテナごとに仮想化されたハードウェアインターフェースを提供する必要があるため、パフォーマンスへの影響が最も大きくなります。
セキュリティ。コンテナの分離機能を強化するほど、セキュリティも向上します。見てきたとおり、サンドボックス型コンテナはLinuxコンテナより安全であり、マイクロVMベースのコンテナはそのどちらよりも安全です。
複雑さと開発期間。Linuxコンテナは比較的シンプルで、本番環境で実行されているコンテナの大半を占めています。優れたツールやドキュメントが豊富にそろっているため、開発期間を短縮し、コンテナ化アプリのデプロイに伴う複雑さを軽減できます。サンドボックス型コンテナやマイクロVMはあまり普及していないため、一般的なツールの多くが対応していません。対応するツールやプラットフォームが少ないことで、開発チームやDevOpsチームはツールに頼らず手作業で対応する必要が増え、開発期間が延びて複雑さも増します。
最終的には、パフォーマンス、セキュリティ、複雑さの間でトレードオフが必要です。たとえば、パフォーマンスを優先すると、セキュリティをある程度犠牲にすることになるかもしれません。一方、セキュリティを優先すると、パフォーマンスを犠牲にし、開発期間の長期化を受け入れる必要があるかもしれません。
コンテナセキュリティ
開発者やDevOpsの担当者は、コンテナ分離の限界を理解するために、コンテナがホストマシンからどのように分離されているかを把握する必要があります。コンテナは開発サイクルの迅速化に役立ちますが、コンテナとアプリケーションの安全を守るため、ベストプラクティスを実践することが重要です。
すべての開発チームとDevOpsチームは、多層防御の戦略を採用すべきです。コンテナ分離は、その対策の一部にすぎません。コードスキャナーとコンテナスキャナーを使ってコードに脆弱性がないことを確認し、コンテナ内のコンテンツを安全に保つことで、システムのセキュリティを強化できます。コンテナ分離は防御策の1つであり、唯一の防御策ではありません。
開発者ファーストのコンテナセキュリティ
Snykは、コンテナイメージとKubernetesワークロードの脆弱性を検出し、自動的に修正します。
