Yakumoのコンテナの脆弱性検知と対応フローの紹介

こんにちは、Yakumoチームの@ueokandeです。 好きなAWSサービスはCloudFormationです。

Yakumoプロジェクトは、US市場にKintoneを展開することをゴールとしたプロジェクトで、 その一環としてUS向けのお客様をターゲットにAmazon Web Service (AWS) 上にKintoneを移行しています。 本日はYakumoチームで取り組んでいるコンテナイメージの脆弱性対応フローとその運用について紹介します。

Quay.ioやAmazon Elastic Container Registry (Amazon ECR) には、コンテナイメージに脆弱性があるかスキャンする機能があります。 しかしセキュリティスキャンだけでは、本番適用されているイメージの脆弱性を管理しにくく、運用が難しいということがわかりました。 そこでYakumoではQuay.ioとGitHubを連携して、本番適用されているイメージの脆弱性を管理できる仕組みを作りました。

コンテナイメージの脆弱性とセキュリティスキャン

コンテナ技術が広く使われるようになり、より簡単に仮想化やデプロイができるようになりました。 しかしコンテナでカプセル化されていても、物理ホストやVM上のOSと同じように、イメージに含まれるパッケージの脆弱性について考える必要があります。 特にコンテナでは、イメージ毎にディストリビューションを選択できるため、物理やVM以上に脆弱性の対象が増えます。

Quay.ioにはコンテナイメージのセキュリティスキャンの機能があります。 つい先日Amazon ECR でもセキュリティスキャンの機能がリリースされました。 Quay.ioとAmazon ECRのセキュリティスキャンは、バックエンドにCoreOS Clairというセキュリティスキャンエンジンを使用しています。 Clairは各Linuxパッケージのバージョンと含まれる脆弱性の一覧を保持しており、定期的に各ディストリビューションが公開している脆弱性情報をクロールしてデータベースを更新します。

セキュリティスキャンを実行すると、コンテナイメージに含まれる脆弱性情報と、影響があるパッケージのバージョンを取得できます。 しかしセキュリティスキャンだけではYakumoの脆弱性対応の運用は難しいという結論になりました。 セキュリティスキャン以外に以下の2つの要件が挙がりました。

  • 本番環境に適用されているコンテナをチェックしたい
  • 脆弱性に対応したか否かを状態管理したい

本番環境に適用されているコンテナをチェックしたい

Yakumoがデプロイするミドルウェアはバージョン番号で管理していません。 Gitレポジトリのmasterにマージされているものが常に本番に適用されます。 本番環境にデプロイされているコンテナイメージは、masterブランチに含まれるソースコードのハッシュ値から計算できます。

イメージタグはmasterブランチもトピックブランチも区別してません。 1つのサービスに対してイメージタグの数も1,000近くあります。 コンテナレジストリ上では、一見どれがリリースされているか判断できません。

Yakumoのコンテナイメージ一覧
Yakumoのコンテナイメージ一覧

もちろん本番適用されたイメージタグとQuay.ioのスキャン結果を1つずつ突き合わせることで、各サービスの脆弱性情報を取得できます。 しかしYakumoがビルドしているコンテナイメージの数は30以上にもなります。 それを1つずつ人手でチェックするのはあまりいい方法ではありません。 そのため本番に適用されているイメージタグの特定と、そのタグに対する脆弱性情報の取得は、自動化がほぼ必須となります。

脆弱性対応したか否かを状態管理したい

もう1つの要件は、脆弱性対応の状態管理です。 発見された脆弱性情報をSlackやKintoneのスレッドに通知することも可能ですが、時間が経過すると対応したかの判別が難しくなります。 そのため各脆弱性情報に対して、対応したか否かの状態を管理できる仕組みが必要です。

脆弱性のあるパッケージが含まれるが、アプリケーションには影響しないという場合もあります。 こういった脆弱性は「対応不要」という状態にして通知しないようにもしたいです。

Yakumoのセキュリティスキャンと対応フロー

上記の要件を満たすために、Yakumoのセキュリティスキャンと状態管理の仕組みを自動化しました。 Yakumoのセキュリティスキャンの構成が以下のようになっています。

Yakumoのセキュリティスキャンと対応フローの図
Yakumoのセキュリティスキャンと対応フローの図

YakumoではコンテナレジストリとしてQuay.ioを採用しています。 また脆弱性の対応状況についてはGitHub Issuesで管理します。 各ステップについて順に説明します。

1. 本番適用されているイメージをスキャン

このステップでは、本番適用されているイメージタグを取得して、Quay.ioからセキュリティスキャンの結果を取得します。 この処理はCircleCI CronJobによって定期実行しています。

本番に適用されているイメージタグは、Gitレポジトリのリビジョンから一意に決まります。 適用されてるイメージタグがわかれば、脆弱性情報をQuay.ioに問い合わせることができます。

2. スキャン結果をGitHub Issuesに反映

検知された脆弱性から、CVEに紐付くIssueをGitHub Issuesに作成します。 Issueのラベルには、Severityや影響のあるイメージが付与されます。

各IssueにはCVEをキーにして各イメージをまとめてあります。 なぜなら、大抵の場合はベースイメージを更新することで対応できるからです。 検出されたCVEのIssueが既にCloseされているとReopenしますが、対応不要と判断したものは wondfixラベルを付与すると、自動でReopenされません。 Kintone本体とYakumoのミドルウェアは、チームごとに管理できるようにIssueを分けてあります。

例えば以下のIssueは、Yakumoチームが管理しているミドルウェアyakumo-nginxyakumo-http-proxyというイメージで検出された脆弱性です。 この脆弱性はすでに対応済みなのでCloseされています。

CircleCIによって作成されるIssue
CircleCIによって作成されるIssue

3. 修正されたイメージをデプロイ

脆弱性が検知されてIssueが作られると、各チームは脆弱性対応をします。 具体的には、使用しているベースイメージを更新したり、対象のパッケージをアップデートするなどです。 そして脆弱性対応済みのイメージをQuay.ioにpushして本番のイメージを更新します。

4. IssueのClose

各チームはコンテナイメージの脆弱性対応が完了すると、該当のIssueをCloseします。 ここは手動でCloseします。 もし仮に誤って別のIssueをCloseしても、ステップ1.で再びReopenされるので大丈夫です。

まとめ

この運用が開始して、常に本番適用されているイメージに脆弱性があるかの判断が簡単になりました。 脆弱性のIssueが空になると、本番のイメージに脆弱性が含まれないと安心できます。

YakumoはCybozu内で初めてのKubernetes本番運用ということで、クラウドネイティブ時代の開発・運用の仕組みも同時に作っています。 その1つの取り組みである、コンテナの脆弱性対応について紹介しました。 今後もサービスへ組み込んだ技術やノウハウがあれば、記事を書きたいと思います。