新しい IBM Developer JP サイトへようこそ!サイトのデザインが一新され、旧 developerWorks のコンテンツも統合されました。 詳細はこちら

コンテナー実行環境を osquery でモニタリングする

コンテナーはその可動性とストレージの効率性から、多用されるようになっています。従来のシステムを対象としたモニタリング・ソリューションは数多くありますが、コンテナー実行環境を対象とした既存のモニタリング・ソリューションはありません。

「KuberNets」と名付けられた私のチームでは、osquery を使用してコンテナー内部の状況に関する可視性を広げる取り組みを開始しました。KuberNets チームを構成する私たちは、コンテナー実行環境の可視性を高めるために利用できるソリューションを調査するという任務を与えられたインターンです。私たちはセキュリティー・チームのためにコンテナーに対するモニタリング機能を拡張し、インターン期間が終了するまでに、デプロイ可能な状態のソリューションを提供するという最終目標を設定しました。

私たちの知る限り、脅威を検出するというニーズを満たす既存のコンテナー・モニタリング・ソリューションはありませんでした。基本的なモニタリングの例としては、プロセスがアクティブになったタイミングをモニタリングする、ファイルが削除されたかどうかをモニタリングする、コンテナーと通信しているネットワーク接続をモニタリングするなどが挙げられます。とりわけ重要なのは、アクティビティーがどのコンテナーから開始されたかを把握することです。Kubernetes は幅広く採用されていて、しかも大規模なオープンソースのクラウド開発コミュニティーがあるにも関わらず、意外なことに、こうしたモニタリングのニーズに完全に対処できるソリューションはありません。コンテナー内で一体何が行われているのかを知る手段はないのでしょうか?

セキュリティーに関してこのような懸念を持っていて、Kubernetes のモニタリングの仕組みを改善しようと目指しているとしたら、この先を読み進めてください。osquery はコンテナー・モニタリングのあらゆるニーズに対応できる万能のソリューションというわけではありませんが、コンテナー・プロセスとネットワーク・アクティビティーを確認できることから、万能のソリューションに向けた一歩前進であることは確かです。

この記事は、IBM のインターンからなるチーム KuberNets がチームのプロジェクトで行った共同作業に基づいています。チームのメンバーは、John Do、Zane Doleh、Tabor Kvasnicka、Joshua Stuifbergen です。

前提条件

この記事を最大限に活用するには、Kubernetes、Docker、コンテナー、SQL、Linux、osquery の知識があると理想的です。

所要時間

この記事は 10 分ほどで読み終えることができます。

コンテナー、Docker、Kubernetes、containerd、proc 疑似ファイルシステム

KuberNets チームのプロジェクトを構成する主なコンポーネントは、コンテナー、Docker、Kubernetes、containerd、Linux proc 疑似ファイルシステムです。これらのコンポーネントのアーキテクチャーについて基礎知識があったため、私たちは osquery を適切に評価することができました。上述のコンポーネントについてすでに十分に理解している場合は、これらのセクションをスキップするのでもかまいません。

コンテナー

コンテナーは、ソフトウェアの標準単位です。コンテナーを使用することで、アプリケーションとインフラストラクチャーを分離し、「Deploy Anywhere (場所を問わずにデプロイ)」手法を完全に活用できます。

アプリケーションとその依存関係は、ビルド時にまとめてイメージにパッケージ化されます。このようにイメージにはアプリケーションをデプロイするために必要なものがすべて含まれるため、どのオペレーティング・システム上にも、どのハードウェア上にもイメージをデプロイできます。イメージは実行時にコンテナーにデプロイされます。すると、そのコンテナーをデプロイするために使用されるエンジンによって、ファイアウォールが構成されるとともに、ユーザーがアプリケーションにアクセスするために必要なネットワーキングが構成されます。

このアーキテクチャーにより、開発者はデプロイメントの詳細よりも、アプリケーションに専念できるようになります。

Docker

Docker は、エンタープライズ・レベルのコンテナーを提供する組織です。開発者は Docker エンジンをソリューションの中心として、アプリケーションを構築、制御、保護することができます。

Docker には、containerd という強力なコマンド・ライン・インターフェースをはじめ、業界最先端のコンテナー・ツールが同梱されています。さらに BuildKit が統合されていて、有用な機能も揃っています。

チームのプロジェクトでは、アクティブなプロセスとネットワーク・アクティビティーを追跡するために、主に Docker を使用してイメージをビルドしました。Docker に統合された BuildKit で Dockerfile を読み取り、イメージのビルドを加速化するという仕組みです。

Kubernetes

Kubernetes は、当初 Google によって開発された、ポッド (Pod) 上のワークロードとサービスを管理するためのオープンソース・プラットフォームです。ポッドは Kubernetes 内での最小単位のソフトウェアであり、1 つ以上のコンテナーからなります。Kubernetes はネットワーキングおよびストレージ・インフラストラクチャーのオーケストレーションを行ってワークロードをサポートします。Kubernetes はコンテナーを一元管理する環境として、アプリケーションをデプロイ、スケーリング、管理するためのコンポーネントとツールのエコシステムを構築しています。

このアーキテクチャーは便利ではあるものの、代償も伴います。それは、攻撃者が Kubernetes の制御を手にすると、Kubernetes 内のコンテナーも制御できてしまうことです。既存のポッドを削除、変更したり、悪意のあるポッドを新しく作成したりと、基本的にあらゆる不正行為を行うことができます。

チームのプロジェクトで焦点としているのは、システムをセキュリティーで保護することではなく、システムに対する可視性をもたらすことによって、潜在的な脅威を識別し、軽減できるようにすることです。

containerd

containerd は現在、業界標準のコンテナー・ランタイムとなっています。この Cloud Native Computing Foundation プロジェクトは Docker と Kubernetes の両方で使用されています。

containerd の役割はコンテナーを管理することであり、コンテナー管理用の API を公開しています。つまり、containerd はコマンド・ライン・インターフェースで使用するようには意図されていません。Kubernetes は以前、コンテナーの管理にデフォルトで Docker を使用していましたが、Docker は内部で containerd を使用することから、現在はデフォルトで containerd を直接使用するようになっています。

proc 疑似ファイルシステム

Linux とその他の Unix ベースのオペレーティング・システムでは、「Everything is a file (すべてをファイルとして扱う)」という手法を採用しています。この手法が重要となる理由は、システムに関するあらゆる情報を、システム上のファイル内で調べられるためです。システム上で実行されているプロセスに関する情報を検索するには、この手法が役立ちます。プロセスに関する情報のすべては、通常は /proc ディレクトリーにマウントされている proc 疑似ファイルシステム、別名 procfs 内で見つかります。

procfs 内では、CPU の情報、メモリーの情報、プロセスの統計情報など、さまざまな情報を調べることができます。ここにはまた、システム上で実行されているすべてのプロセスに関する情報も格納されます。/proc ディレクトリー内にはプロセス ID ごとにフォルダーがあり、各フォルダー内から、その特定のプロセスに関するさらに具体的な情報 (プロセス名、実行可能ファイルのパス、ユーザー ID、グループ ID など) を入手することができます。プロセス ID ごとのフォルダーには、そのプロセスのネットワーク情報も格納されています。したがって、Linux ベースのシステムをモニタリングする際は、このファイルシステムへのアクセスが必須となります。

osquery の概要

osquery は、Facebook により開発され、2014 年にオープンソース化されたシステム・モニタリング・ソリューションです。このソリューションはオペレーティング・システムをリレーショナル・データベースに構造化することによって、SQL によるクエリーを可能にします。osquery は、Mac OS X、Windows、およびよく使われている多くの Linux ディストリビューションで使用できます。

osquery ではオペレーティング・システムのモニタリング・ルーチンを容易にするために、情報の検索方法を変更するという方法を取っています。osquery では、事前にパッケージ化されたテーブルに対するクエリーを実行し、実行中のプロセスやハードウェア・イベントを表示するなどして、マシンのパフォーマンスと状態を簡単に確認することができます。osquery コンテナーからクエリーを実行すると、ノード上の他のコンテナーの実行中のプロセスとネットワーク・トラフィックに関する情報が返されます。この情報は、変則的なプロセスや通常とは異なる接続に関する洞察をもたらします。

以降のセクションで、私たちのチームが osquery コンテナー・モニタリング・ソリューションを機能させるまでに辿った過程を説明します。ですがその前に、osquery 内部の仕組みについて説明しておきます。

osquery のアーキテクチャー

以下の図に示されているように、osquery では仮想テーブルとイベント・テーブルという 2 種類のテーブルを使用します。

alt

仮想テーブルは、クエリーの実行時に生成されます。仮想テーブルの編成は syscall、OS API、および構成ファイルに基づきます。このパラダイムは、あらゆるタイプのデータに適用できるわけではありません。例えば、ファイルシステム全体のモニタリングは実行不可能です。クエリーが実行されるたびにテーブルが生成されるとしたら、過剰なオーバーヘッドが生じます。そのため、osquery ではイベント・システムを使用してデータを保管し、既存のテーブルに基づいてリクエストされた情報を取得できるようになっています。

イベント・システムはイベントが発生するとデータを記録し、そのデータを RocksDB インスタンス内に保管します。イベント・テーブルのいずれかに対するクエリーが実行されると、仮想テーブルはクエリー実行時にシステムから収集されたデータから生成されるのではなく、RocksDB データから生成されます。この手法は、より集約的なモニタリングを可能にします。つまり、ほぼリアルタイムのモニタリングです。process_events テーブルでは Linux Audit System を使用してカーネルからイベントを受信し、それらのイベントを RocksDB 内に記録します。process_events に対してクエリーが実行される際は、RocksDB 内に記録されたデータからテーブルが生成されます。

生成されたテーブルは SQLite エンジンによって処理されて、リレーショナル・データベースの通常の機能を使用できるようになります。データの収集には、いくつかの最適化が適用されます。WHERE SQL 句を使用したテーブルのクエリーでは、osquery がフィルタリングを処理して不要なデータが収集されないようにする場合があります。すべてのデータを収集して SQLite エンジンがそのほとんどを破棄するよりも、この手法のほうが効率的です。

実装

osquery を実行するには、2 つのモードを使用できます。1 つは対話型シェルとして実行するモード (osqueryi) 、もう 1 つはデーモンとして実行するモード (osqueryd) です。osquery デーモンosqueryd という用語は同じ意味で使用されます。対話型シェルを使用すると、手作業でシステムのクエリーを実行できます。このシェルは、クエリーをテストする際、またはシステムに関する簡単な情報を収集する際には役立ちますが、このプロジェクトでは osquery デーモンを使用しました。osquery デーモンは、定期的にクエリーを実行するように構成することができます。私たちはその目的で、新しいクエリーを構成ファイルに追加しました。

完全なクエリー結果ではなく、クエリー結果の差分だけをログに記録するように osquery を構成することもできます。この場合、クエリー結果は diff エンジンにフィードされます。そこで現在のクエリー結果と前のクエリー結果が比較されて追加または削除された行が判断され、(完全な結果ではなく) これらの差分が出力されるという仕組みです。多数のシステムでデータを転送する場合は、この手法のほうがスケーラビリティーに優れています。私たちがプロジェクトで使用した osqueryd は、デフォルトで diff エンジンを使用します。

osquery で主な課題となったのは、本来 osquery に意図されている方法ではない方法で使用する必要があったことです。osquery でモニタリングできるシステムは、osquery をホストしているシステムに限られます。理想的には、osquery を DaemonSet としてデプロイし、各ノード上のすべてのコンテナーをモニタリングできるようにする必要があります。けれども、このデプロイを機能させるためには、osquery にコンテナー外部を可視にさせなければなりません。

さらに、私たちが使用していた Kubernetes 環境もこの目標を達成する障害となりました。osquery には Docker を確認するためのテーブルが組み込まれていますが、Kubernetes はデフォルトで Docker を使用しなくなっていたからです。Kubernetes は Docker を抽象化し、コンテナー・ランタイム環境として containerd を使用するようになっています。そこで、私たちは osquery を評価するために、同じく containerd を採用している IBM Cloud Kubernetes サービス・デプロイメントを利用しました。

必要な調査を行った末、osquery の可視性を広げる方法を発見しました。ノードのホスト・オペレーティング・システムは Linux ベースであるため、proc 疑似ファイルシステムを使用します。さらに、osquery ソース・コードを調査すると、単純に /proc を列挙してプロセス情報を収集しているだけであることがわかりました。私たちはこの情報と、すべてのコンテナーはホストの procfs を列挙する containerd-shim プロセスに過ぎないという事実を利用することにしました。

私たちは、現在のノード上で実行されているコンテナー・プロセスに関する情報を取得することに成功しました。具体的には、ノードの proc 疑似ファイルシステムを、osquery が /host/proc としてデプロイされているコンテナーにマウントした上で (前の図を参照)、Go で osquery 拡張機能を作成しました。この拡張機能で、/host/proc を読み取り、ノードのプロセス情報を新しい host_processes テーブル内に返し、実質的にプロセス・テーブルを再現します。 この拡張機能を作成するために、(Kolide が作成した) osquery-go リポジトリーをテンプレートとして使用しました。

以下のコードは、/host/proc からデータをプルする新しい host_processes テーブルを示しています。

alt

host_processes テーブルには、プロセス・テーブルから取得された情報が再現されます。以下のスクリーン・キャプチャーに、取得される情報の例が示されています。

alt

プロセスを確認できるようになったので、次は、ネットワーク・アクティビティーをモニタリングできるようにする作業に取り掛かりました。

ネットワーク・アクティビティーの情報も procfs 内にあるため、この情報を格納するテーブルを追加するのは簡単なはずです。けれども、拡張機能に使用したライブラリーでは、ネットワーク情報の列挙をネイティブにサポートしていません。このことから、osquery コード・ベース内の /proc のすべてのインスタンスを /host/proc で置き換えて、ソースからビルドするというアイデアを思いつきました。この変更によって、求めていた結果になりました。

プロセスが osquery に可視になった後、別の問題が生じました。それは、コンテナー・プロセスは確認できても、そのプロセスがどのコンテナーに属しているのかを確認する手段がないことです。このことから、プロセスをモニタリングするだけでなく、Kubernetes API を使用してポッドとコンテナーの情報も取得するように osquery を拡張しました。こうすれば、2 つの異なるクエリーの情報を突き合わせることで、プロセスで指定されたコンテナー ID をコンテナー名と一致させることができます。

以下のサンプル・コードは、kubernetes_pods テーブルが Kubernetes API から取り込む情報を示しています。

func KubernetesPodsColumns() []table.ColumnDefinition {
    return []table.ColumnDefinition{
        table.TextColumn("uid"),
        table.TextColumn("name"),
        table.TextColumn("namespace"),
        table.IntegerColumn("priority"),
        table.TextColumn("node"),
        table.TextColumn("start_time"),
        table.TextColumn("labels"),
        table.TextColumn("annotations"),
        table.TextColumn("status"),
        table.TextColumn("ip"),
        table.TextColumn("controlled_by"),
        table.TextColumn("owner_uid"),
        table.TextColumn("qos_class"),
    }
}

以下のサンプル・コードは、kubernetes_containers テーブルが Kubernetes API から取り込む情報を示しています。

func KubernetesContainersColumns() []table.ColumnDefinition {
    return []table.ColumnDefinition{
        table.TextColumn("id"),
        table.TextColumn("name"),
        table.TextColumn("pod_uid"),
        table.TextColumn("pod_name"),
        table.TextColumn("namespace"),
        table.TextColumn("image"),
        table.TextColumn("image_id"),
        table.TextColumn("state"),
        table.IntegerColumn("ready"),
        table.TextColumn("started_at"),
        // table.TextColumn("env_variables"),
    }
}

次に目標としたのは、この 2 つのテーブル内の情報を結合して 1 つのログとして確認できるようにするクエリーを作成することです。私たちはこの目標の達成に向けて、現在も取り組んでいるところです。

Dockerfile と拡張機能の構成に関する注記: 私たちは osqueryd で拡張機能を自動ロードするために、osquery-go リポジトリーのドキュメントに従いました。extension.load ファイルを作成し、そのファイル内に拡張機能のパスを設定し、拡張機能名を変更して名前に .ext を含めました。Dockerfile 内の命令により、osqueryd が起動時に検索するデフォルトのディレクトリーに構成ファイル、拡張機能を自動ロードするためのファイル、osqueryd バイナリー、カスタム拡張機能を配置しました (以下のスクリーン・キャプチャーを参照)。拡張機能と osqueryd をエラーなしで実行するには、この 2 つに実行権限を付与する必要があります。

スクリーン・キャプチャー

拡張機能をロードするには、構成ファイル内で 2 つのフラグを使用する必要があります。拡張機能を有効にするフラグと、自動ロード拡張機能の場所を識別するフラグです (以下のスクリーン・キャプチャーを参照)。その他多数の重要なフラグについては、osquery のドキュメントを参照してください。

スクリーン・キャプチャー

osquery を構成することで、ファイルにでもエンドポイントにでも、さまざまな方法でログを出力することができます。私たちはファイルにログを出力するように osquery を構成するために、Filebeat モジュールをインストールしてから、データを Elasticsearch に転送しました。こうすれば、Kibana 内でログを表示できます。

デモンストレーション

デプロイ済みコンテナー上での通常とは異なるネットワーク接続や不明なプロセスは、いずれも疑わしいアクティビティーと見なされます。このセクションでは、osquery が Netcat バックドア・シェルを実行する攻撃者のアクションを捕捉する仕組みを説明します。ここで取り上げる例は、osquery が同じノード上の別のコンテナーによるプロセスとネットワーク・アクティビティーをどのように検出するかをデモンストレーションするだけに過ぎません。

以下のスクリーン・キャプチャーを見るとわかるように、このコマンドは Netcat リスナーを開始します。コンテナーとなる予定のこのリスナーをデプロイすると、これがターゲットになります。数値 4444 は、このリスナーが listen するローカル・ポートです。攻撃者はこのポートに接続して、bash シェル・セッションを開始します。

Netcat リスナーを開始するコマンドを示す画面のスクリーン・キャプチャー

まず始めに、以下のコマンドを使用して nc-app という Netcat アプリケーションをデプロイします。

kubectl create deployment nc-app --image=<docker_image>

create deployment コマンドを示す画面のスクリーン・キャプチャー

ポッドが稼動中であるかどうかを確認するために、以下のコマンドを使用します。

kubectl get pods

get pods コマンドを示す画面のスクリーン・キャプチャー

Netcat サービスを公開して、クラスター外部からの接続がコンテナーに到達するようにして、ポート 4444 を開きます。それには、以下のコマンドを使用します。

kubectl expose deployment nc-app --type=LoadBalancer --name=nc-service --port 4444

外部 IP も作成されます。

expose deployment コマンドを示すスクリーン・キャプチャー

サービスがアクティブであることを確認するために、以下のコマンドを使用します。

kubectl get services nc-service

外部の環境から、サービスの外部 IP を使用して以下の Netcat クライアント・コマンドを実行します。

nc 46.102.66.23 4444

外部 IP を使用した Netcat クライアント・コマンドを示す画面のスクリーン・キャプチャー

クエリー host_processes_query からの出力を確認できます。

host_process_query の出力を示す画面のスクリーン・キャプチャー

nc-app コンテナーのコマンド・ライン /bin/sh -c nc -lvp 4444 -e /bin/bash とプロセス sh の名前が示されています。この情報は、osquery コンテナーから読み取られたカスタム host_processes テーブルから取得されたものです。この情報の重要性は、あるコンテナーが別のコンテナーのアクションをレポートできるようになったという点にあります。

以下に、kubernetes_container クエリーによる出力を示します。

kubernetes_container クエリーの出力を示す画面のスクリーン・キャプチャー

この情報には、コンテナーの名前、ポッド名、名前空間、イメージ、状態、コンテナーが起動した日時が含まれています。

以下に、kubernetes_pod クエリーによる出力を示します。

kubernetes_pod クエリーの出力を示す画面のスクリーン・キャプチャー

この情報には、ポッドの UID、名前、名前空間、ノード、ステータス、起動した時刻、ポッド IP が含まれています。

以下に示す IP 172.30.71.203 は、kubernetes_pod ログに記録されたポッド IP と一致することに注目してください。

ポッド IP と一致する IP 172.30.71.203 を示す画面のスクリーン・キャプチャー

まとめ

私たちの目標は、コンテナーに関する可視性を高めることでした。osquery は一般にホスト・システム上にデプロイされますが、その場合、コンテナー内での可視に関して制約が生じます。 そこで、私たちはネットワーク・アクティビティーが可視になるように変更を加え、感染したコンテナーから別のコンテナーへの変則的な接続を検出できるようにしました。また、アクティブなプロセスと、そのプロセスを開始したコンテナーも確認できるようにしました。

このように osquery を変更することによって、コンテナー内での実際の状況がわかるようになりましたが、もちろん完全な可視性が得られたわけではありません。

コンテナーに関する可視性を広げたものの、さらに手を加える必要があります。osquery の重要な機能はイベント・システムですが、これらのテーブルが使用する Linux Audit System では、システムに直接アクセスできることを要件としています。したがって、イベント・システムを使用してほぼリアルタイムのモニタリングを可能にすることはできませんでした。Linux Audit System と同じ方法で、しかも外部から Linux システムをモニタリングするためのシステムがまだ存在していなければ、こうしたシステムを新しく開発しなければなりません。そうすれば、そのシステムを使用するように osquery を拡張できます。

osquery に魅力を感じられないとしたら、自分で試してその機能を探ってください。osquery をよく知らない場合は、これをインストールして、クラウド環境のモニタリングに役立つかどうか判断することをお勧めします。osquery.slack.com という活発な slack コミュニティーも調べてください。また、osquery に独自仕様の拡張を適用している、Uptycs という会社のサイトを調べることもお勧めします。

利用できるコンテナー・モニタリング・ソリューションは他にも多数あるので、ハイブリッド手法でモニタリングをセットアップすることを検討するのもよいでしょう。私たちが調べてわかったように、osquery は Docker を抽象化してコンテナーをモニタリングするように設計されてはいませんが、この設計に対処する方法が見つかりました。この例は、従来のモニタリング・ソシューションを使用して別の場合に応用できるはずです。