Docker のセキュリティ¶
Docker のセキュリティには、主に3つの検討項目があります。
- カーネルに起因するセキュリティと、カーネルがサポートする名前空間と cgroups について
- Docker デーモン自身が直面する攻撃について
- コンテナ設定プロファイル(デフォルトでもユーザによってカスタマイズされた場合も含む)における抜け道について
- カーネルのセキュリティ「硬化」(hardening)機能と、コンテナへの対応。
カーネルの名前空間¶
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 を使ったコンテナ(とアプリケーション)を実行するとは、Docker デーモンの稼働を意味します。このデーモンは現時点で root
特権が必要であり、それゆえ、いくつか重要な点に配慮が必要です。
まずはじめに、 信頼する利用者だけ、Docker デーモンに対するアクセスを許可するべき です。これは Docker がもたらす強力な機能を直接扱うためです。特に、Docker は Docker ホストとゲストコンテナ間でディレクトリを共有できます。そして、それにより、コンテナ内に対する適切なアクセス権限が無くても、ディレクトリを使えるようになる可能性があります。つまりコンテナの /host
ディレクトリは、ホスト上の /
ディレクトリとしても実行可能です。それだけではありません。コンテナは何ら制限を受けずに、ホスト上のファイルシステム上に対する修正が可能になります。これは仮想化システムによるファイルシステム・リソースの共有に似ています。仮想マシン上における自分のルート・ファイルシステム(ルート・ブロック・デバイスも同様)の共有を阻止する方法はありません。
これはセキュリティに重大な影響を及ぼします。例えば、Docker の API を通してウェブ・サーバ用コンテナをプロビジョンしたいとします。通常通りパラメータの確認に注意を払うべきです。ここでは、悪意のあるユーザが手の込んだパラメータを使い、Docker が余分なコンテナを作成不可能にしてください。
この理由により、REST API エンドポイント(Docker CLI が Docker デーモンとの通信に使います)が Docker 0.5.2 で変更されました。現在は 127.0.0.1 上の TCP ソケットに代わり、 UNIX ソケットを使います(最近はローカルのマシン上の Docker に対して、仮想マシンの外から直接クロスサイト・リクエスト・フォージェリ、CSRF を行う傾向があります)。伝統的な Unix パーミッションを確認し、ソケットに対するアクセスを制限するような管理が必要です。
明示的に HTTP 上で REST API を晒すことも可能です。しかし、そのように設定すべきではありません。上記で言及したセキュリティ実装のため、信頼できるネットワークや VPN 、 stunnel
やクライアント SSL 証明が利用できる所でのみ使うべきです。より安全にするためには HTTPS と証明書 を利用できます。
また、デーモンは入力に関する脆弱性を潜在的に持っています。これはディスク上で docker load
、あるいはネットワーク上で docker pull
を使いイメージを読み込む時です。これはコミュニティにおける改良に焦点がおかれており、特に安全に pull
するためです。これまでの部分と重複しますが、 docker load
はバックアップや修復のための仕組みです。しかし、イメージの読み込みにあたっては、現時点で安全な仕組みではないと考えられていることに注意してください。Docker 1.3.2 からは、イメージは Linux/Unix プラットフォームの chroot サブ・プロセスとして展開されるようになりました。これは広範囲にわたる特権分離問題に対する第一歩です。
最終的には、Docker デーモンは制限された権限下で動作するようになるでしょう。それぞれが自身の(あるいは限定された) Linux ケーパビリティ(capability;「能力」や「機能」の意味)、仮想ネットワークのセットアップ、ファイルシステム管理といった、サブプロセスごとに委任したオペレーションを監査できるようになることを期待しています。
なお、Docker をサーバで動かす場合は、サーバ上で Docker 以外を動かさないことを推奨します。そして、他のサービスは Docker によって管理されるコンテナに移動しましょう。もちろん、好きな管理ツール(おそらく SSH サーバでしょう)や既存の監視・管理プロセス(例: NRPE、collectd、等)はそのままで構いません。
Linux カーネルのケーパビリティ¶
デフォルトでは Docker はケーパビリティ(capability;「能力」や「機能」の意味)を抑えた状態でコンテナを起動します。つまり、これはどのような意味でしょうか。
ケーパビリティとは、「root」か「root以外か」といったバイナリの二分法によって分類する、きめ細かなアクセス制御システムです。(ウェブサーバのような)プロセスがポート 1024 以下でポートをバインドする必要がある時、root 権限でなければ実行できません。そこで net_bind_service
ケーパビリティを使い、権限を与えます。他にも多くのケーパビリティがあります。大部分は特定の条件下で root 特権を利用できるようにするものです。
つまり、コンテナのセキュリティを高めます。理由を見ていきましょう!
あなたの平均的なサーバ(ベアメタルでも、仮想マシンでも)が必要とするのは、root として実行される一連のプロセスです。典型的なものに SSH、cron、syslogd が含まれるでしょう。あるいは、ハードウェア管理ツール(例:load モジュール)、ネットワーク設定ツール(例:DHCP、WPA、VPN を取り扱うもの)、等々があります。ですが、コンテナは非常に異なります。なぜなら、これらのタスクのほぼ全てが、コンテナの中という基盤上で処理されるからです。
- SSH 接続は、 Docker ホストのサーバ上を管理する典型的な手法です。
cron
は、必要があればユーザ・プロセスとして実行可能です。プラットフォーム上のファシリティを広範囲に使うのではなく、専用、もしくはアプリケーションが個別に必要なサービスをスケジュールします。
- ログ管理もまた Docker の典型的な処理であり、あるいはサードパーティー製の Loggly や Splunk を使うでしょう。
- ハードウェア管理には適していません。これはコンテナ内で
udevd
や同等のデーモンを実行できないためです。
- ネットワーク管理はコンテナの外で行われので、懸念されうる事項を分離します。つまり、コンテナでは
ifconfig
、route
、ip
コマンドを実行する必要がありません(ただし、コンテナでルータやファイアウォール等の振る舞いを処理させる場合は、もちろん除きます)。
これらが意味するのは、大部分のケースにおいて、コンテナを「本当の」 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 コンテナを堅牢にするものです。
Docker 1.10 以降は Docker デーモンがユーザ名前空間(User Namespaces)を直接サポートしました。この機能により、コンテナ内の root ユーザをコンテナ外の uid 0 以外のユーザに割り当て(マッピング)できるようになります。コンテナからブレイクアウト(脱獄)する危険性を軽減する手助けとなるでしょう。この実装は利用可能ですが、デフォルトでは有効ではありません。
こちらの機能に関するより詳しい情報は daemon コマンド のリファレンスをご覧ください。Docker におけるユーザ名前空間の実装に関する詳細情報は こちらのブログ投稿 をご覧ください。