Docker セキュリティ

Docker のセキュリティを検討するにあたり、主に3つの項目があります。

  • カーネルに起因するセキュリティと、カーネルがさぽーとする名前空間と cgroups について
  • Docker デーモン自身が直面する攻撃について
  • コンテナ設定プロファイル(デフォルトでもユーザによってカスタマイズされた時も)における抜け道について
  • カーネルのセキュリティ「硬化」機能と、コンテナへの対応。

カーネルの名前空間

Docker コンテナは LXC コンテナに非常に似ており、類似のセキュリティ機能を持っています。コンテナを docker run で起動すると、その後ろ側では Docker はコンテナ向けの名前空間とコントロール・グループを作成します。

名前空間は、一流かつ最も簡単な方法で分離(isolation)を提供します。これによりコンテナの中で実行しているプロセスは、他のコンテナやホスト上のプロセスから見えなくなり、影響すら受けません。

各コンテナは自分自身のネットワーク・スタックを持ちます 。つまり、コンテナはソケットや他のコンテナのインターフェースに対する特権(privileged)アクセスが得られません。もちろん、ホストシステムが適切に設定されている必要があります。そうしておけば、コンテナが相互に適切なネットワーク・インターフェースを通して通信できるようになります。ホストの外と通信できるのも同様です。コンテナに対して公開用のポートを指定するか、リンク機能 を使うことで、コンテナ間での IP 通信が許可されます。お互いに ping できるようになり、UDP パケットの送受信や、TCP 接続が確立されます。しかし、必要があれば制限を設けられます。ネットワーク・アーキテクチャの視点からすると、全てのコンテナは特定のホスト上のブリッジ・インターフェースを備えています。つまりこれは、物理マシン上で共通のイーサネット・スイッチを使っているのと同じような状態を意味します。それ以上でも、それ以下でもありません。

カーネルの名前空間を提供するコードやプライベート・ネットワーキングの成熟度ですか? カーネルの名前空間は カーネル 2.6.15 と 2.6.26 の間 に導入されました。これの意味するところは、2008年6月にリリースされた(2.6.26 がリリースされたのは、今から7年前です)名前空間のコードは、多数のプロダクション・システム上で動作・精査されてきました。そして、さらにもう1つ。名前区間コードの設計と発想はやや古いものです。名前空間が効果的に実装された例としては OpenVZ があり、カーネルのメインストリームとしてマージされたこともありました。OpenVZ の初期リリースは 2005 年であり、設計と実装は、多少成熟しているでしょう。

コントロール・グループ

コントロール・グループは Linux コンテナにおけるもう1つ重要なコンポーネントです。リソースの計測と制限を実装しています。これらは多くの便利なメトリクス(監視上の指標)を提供するだけでなく、各コンテナが必要な共有リソース(メモリ、CPU、ディスク I/O)の割り当ての保証にも役立ちます。さらに重要なのは、単一のコンテナが膨大なリソースを消費してシステムダウンを引き起こさない点です。

あるコンテナが他のコンテナからアクセスされたり、データや処理を受け取ることに対する制限はできません。これはあらゆるサービス拒否(denial-of-service)攻撃の本質です。特に重要なのはマルチテナントなプラットフォーム、たとえばパブリックやプライベートな PaaS において、特定のアプリケーションが過った動作をしたとしても、一定の稼働(とパフォーマンス)を保証するために使えます。

コントロール・グループも同様に、以前から存在していました。コードは 2006 年から書き始めら、カーネルに 2.6.24 で初めてマージされました。

Docker デーモンが直面する攻撃

Docker を使ったコンテナ(とアプリケーション)の実行とは、Dockre デーモンが動かしていることを意味します。このデーモンは、現時点では root 特権が必要であり、それゆえ、いくつか重要な点に配慮が必要です。

まずはじめに、 信頼する利用者だけ、Docker デーモンに多する制御を許可するべき です。これは Docker がもたらす強力な機能による直接的な影響です。特に、Docker は Docker ホストとゲストコンテナ間でディレクトリを共有出来るようにします。そして、それにより、コンテナ内に対する適切なアクセス権限の制限なく、ディレクトリを使えるようになります。つまり /host ディレクトリで開始したコンテナは、ホスト上の / ディレクトリとしても実行可能です。そして、コンテナは何ら制限なく、ホスト上のファイルシステム上に対する修正が可能になります。これは仮想化システムによるファイルシステム・リソースの共有に似ています。仮想マシン上における自分のルート・ファイルシステム(root ブロック・デバイスも同様)の共有を阻止する方法はありません。

これはセキュリティに重大な影響を及ぼします。例えば、Docker の API を通してウェブ・サーバ用コンテナをプロビジョンしたいとします。通常通りパラメータの確認に注意を払うべきです。ここでは、悪意のあるユーザが手の込んだパラメータを使い、Docker が余分なコンテナを作成できないようにします。

この理由により、REST API エンドポイント(Docker CLI が Docker デーモンとの通信に使います)は Docker 0.5.2 で変更されました。現在は UNIX ソケットを 127.0.0.1 上の TCP ソケットの代わりに使います(最近はローカルのマシン上の Docker に対して、仮想マシンの外から直接クロスサイト・スクリプティング攻撃を行う傾向があります)。伝統的な Unix パーミッションを確認し、ソケットに対するアクセスを制限するような管理が必要です。

明示的に HTTP 上で REST API を晒すことも可能です。しかし、実行すべきではありません。上記で言及したセキュリティ実装のため、信頼できるネットワークや VPN 、 stunnel やクライアント SSL 証明が利用できる所でのみ使うべきです。より安全にするためには HTTPS と証明書 を利用できます。

また、デーモンは入力に関する脆弱性を潜在的に持っています。これはディスク上で docker load 、あるいはネットワーク上で docker pull を使いイメージを読み込むときです。これはコミュニティにおける改良に焦点がおかれており、特に安全に pull するためです。これまでの部分と重複しますが、 docker load はバックアップや修復のための仕組みですが、イメージの読み込みにあたっては現時点で安全な仕組みではないと考えられていることに注意してください。Docker 1.3.2 からは、イメージは Linux/Unix プラットフォームの chroot サブ・プロセスとして展開されるようになりました。これは広範囲にわたる特権分離問題に対する第一歩です。

最終的には、Docker デーモンは制限された権限下で動作するようになるでしょう。それぞれが自身の(あるいは限定された) Linux 許容範囲、仮想ネットワークのセットアップ、ファイルシステム管理といいった、サブプロセス毎に委任したオペレーションを監査できるようになることを期待しています。

最後に、Docker をサーバで動かす場合は、サーバ上で Docker 以外を動かさないことを推奨します。そして、他のサービスは Docker によって管理されるコンテナに移動しましょう。もちろん、好きな管理ツール(おそらく SSH サーバでしょう)や既存の監視・管理プロセス(例: NRPE、collectd、等)はそのままで構いません。

Linux カーネルのキャパビリティ

デフォルトでは Docker はキャパビリティを抑えた状態でコンテナを起動します。つまり、どういうことでしょう?

キャパビリティとは、「root」か「root以外か」といったバイナリの二分法によって分類する、きめ細かなアクセス制御システムです。(ウェブサーバのような)プロセスがポート 1024 以下でポートをバインドする必要があるとき、root 権限でなければ実行できません。そこで net_bind_service キャパビリティを使い、権限を得られます。他にも多くのキャパビリティがあります。大部分は特定条件下で root 特権を利用できるようにするものです。

つまり、コンテナのセキュリティを高めます。何故か見ていきましょう!

あなたの平均的なサーバ(ベアメタルでも、仮想マシンでも)が必要とするのは、root として実行される一連のプロセスです。典型的なものに SSH、cron、syslogd が含まれるでしょう。あるいは、ハードウェア管理ツール(例:load モジュール)、ネットワーク設定ツール(例:DHCP、WPA、VPN を取り扱うもの)、等々があります。ですが、コンテナは非常に異なります。なぜなら、これらのタスクのほぼ全てが、コンテナの中という基盤上で処理されるからです。

  • SSH アクセスは Docker ホストのサーバ上を管理する典型的な手法です。
  • cron は、必要があればユーザ・プロセスとして実行可能です。プラットフォーム上のファシリティを広範囲に使うので亜歯無く、専用、もしくはアプリケーションが個別に必要なサービスをスケジュールします。
  • ログ管理もまた Docker の典型的な処理であり、あるいはサードパーティー製の Loggly や Splunk を使うでしょう。
  • ハードウェア管理には適していません。これはコンテナ内で udevd や同等のデーモンを実行できないためです。
  • ネットワーク管理はコンテナの外で行われので、懸念されうる事項を分離します。つまり、コンテナでは ifconfigrouteip コマンドを実行する必要がありません(ただし、コンテナでルータやファイアウォール等の振る舞いを処理させる場合は、もちろん除きます)。

これらが意味するのは、大部分のケースにおいて、コンテナを「本当の」 root 特権で動かす必要は 全くない ということです。それゆえ、コンテナはキャパビリティの組み合わせを減らして実行できるのです。つまり、コンテナ内の「root」は、実際の「root」よりも権限が少ないことを意味します。例えば、次のような使い方があります。

  • 全ての「mount」操作を拒否
  • raw ソケットへのアクセスを拒否(パケット・スプーフィングを阻止)
  • ファイルイステムに関するいくつかの操作を拒否(新しいデバイス・ノードの作成、ファイル所有者の変更、immutable フラグを含む属性の変更)
  • モジュールの読み込みを禁止
  • などなど

これが意味するのは、侵入者がコンテナ内で root に昇格しようとしても、深刻なダメージを与えるのが困難であり、ホストにも影響を与えられません。

通常のウェブ・アプリケーションには影響を与えません。しかし、悪意のあるユーザであれば、自分たちが自由に使える武器が減ったと分かるでしょう! Docker は 必要に応じて 全てのキャパビリティを除外し、ブラックリストからホワイトリストに除外する方法も使えます。利用可能なキャパビリティについては、 Linux の man ページ をご覧ください。

Docker コンテナ実行にあたり、最も重要なリスクというのは、デフォルトのキャパビリティのセットとコンテナに対するマウントにより、不完全な分離(独立性、あるいは、カーネルの脆弱性と組み合わせ)をもたらすかもしれない点です

Docker はキャパビリティの追加と削除をサポートしますので、デフォルトで何も無いプロファイルも扱えます。これにより、キャパビリティが削除されても Docker は安全ですが、キャパビリティを追加するときは安全が低下します。利用にあたってのベストプラクティスは、各プロセスが明らかに必要なキャパビリティを除き、すべて削除することです。

その他のカーネル・セキュリティ機能

キャパビリティは、最近の Linux カーネルが提供する様々なセキュリティ機能の1つです。他にも既存のよく知られている TOMOYO、AppArmor、SELinux、GRSEC のようなシステムを Docker で使えます。

現時点の Docker はキャパビリティの有効化しかできず、他のシステムには干渉できません。つまり、Docker ホストを堅牢にするには様々な異なった方法があります。以下は複数の例です。

  • カーネルで GRSEC と PAX を実行できます。これにより、コンパイル時と実行時の安全チェック機能をもたらします。アドレスランダム化のような技術に頼る、多くの exploit を無効化します。Docker 固有の設定は不要です。コンテナとは独立して、システムの広範囲にわたるセキュリティ機能を提供します。
  • ディストリビューションに Docker コンテナに対応したセキュリティ・モデル・テンプレートがあれば、利用可能です。たとえば、私たちは AppArmor で動作するテンプレートを提供していますし、Red hat は Docker 対応の SELinux ポリシーを提供しています。これらのテンプレートは外部のセーフティーネットを提供します(キャパビリティと大いに重複する部分もありますが)。
  • 好みのアクセス管理メカニズムを使って、自分自身でポリシーを制限できます。

Docker コンテナと連携する多くのサードパーティー製ツールが提供されています。例えば、特別なネットワーク・トポロジーや共有ファイルシステムです。これらは Docker のコアの影響をうけず、既存の Docker コンテナを堅牢にするものです。

直近の Linux 名前空間に対する改良によって、新しいユーザ名前空間の力を使い、まもなく root 特権無しに全てのコンテナ機能が使えるようになるでしょう。詳細は こちら で扱っています。さらに、これはホストとゲストに関する共用ファイルシステムによって引き起こされる問題も解決できるかもしれません。これはユーザ名前空間がコンテナ内のユーザをホスト上のユーザ(rootも含まれます)に割り当て(マッピング)できるようにするためです。

今日、Docker はユーザ名前空間を直接サポートしていません。しかし、Docker コンテナの実行をサポートしているカーネルでは利用可能なものです。直接使うには syscall をクローンするか、 ‘unshare’ ユーティリティを使います。これらを使い、ユーザ名前空間が提供するアーティフィカル・キャパビリティ・セット(artificial capabilities set)から、特定のユーザに対するキャパビリティを無効化できることが分かるでしょう。しかしながら、このアーティフィカル・キャパビリティ・セットを unshare で使う時は、ユーザ名前空間で制限するために ‘capsh’ が必要になるかもしれません。

最終的には、Docker が直接ユーザ名前空間をサポートし、コンテナ上のプロセス堅牢化を簡単に行えるようになるでしょう。

まとめ

Docker コンテナはデフォルトでも安全ですが、とりわけコンテナ内でプロセスを権限のないユーザ(例: root 以外のユーザ)で実行する時に、注意が必要です。

AppArmor、SELinux、GRSEC など任意の堅牢化ソリューションを有効化することで、さらに安全なレイヤーを追加できます。

最後ですが疎かにできないのは、他のコンテナ化システムのセキュリティ機能に興味があれば、Docker と同じように、シンプルにカーネル機能を実装しているのがわかるでしょう。私たちは皆さんからの問題報告、プルリクエスト、メーリングリストにおけるやりとりを歓迎します。