こんにちは、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近くあります。 コンテナレジストリ上では、一見どれがリリースされているか判断できません。
もちろん本番適用されたイメージタグとQuay.ioのスキャン結果を1つずつ突き合わせることで、各サービスの脆弱性情報を取得できます。 しかしYakumoがビルドしているコンテナイメージの数は30以上にもなります。 それを1つずつ人手でチェックするのはあまりいい方法ではありません。 そのため本番に適用されているイメージタグの特定と、そのタグに対する脆弱性情報の取得は、自動化がほぼ必須となります。
脆弱性対応したか否かを状態管理したい
もう1つの要件は、脆弱性対応の状態管理です。 発見された脆弱性情報をSlackやKintoneのスレッドに通知することも可能ですが、時間が経過すると対応したかの判別が難しくなります。 そのため各脆弱性情報に対して、対応したか否かの状態を管理できる仕組みが必要です。
脆弱性のあるパッケージが含まれるが、アプリケーションには影響しないという場合もあります。 こういった脆弱性は「対応不要」という状態にして通知しないようにもしたいです。
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-nginx
とyakumo-http-proxy
というイメージで検出された脆弱性です。
この脆弱性はすでに対応済みなのでCloseされています。
3. 修正されたイメージをデプロイ
脆弱性が検知されてIssueが作られると、各チームは脆弱性対応をします。 具体的には、使用しているベースイメージを更新したり、対象のパッケージをアップデートするなどです。 そして脆弱性対応済みのイメージをQuay.ioにpushして本番のイメージを更新します。
4. IssueのClose
各チームはコンテナイメージの脆弱性対応が完了すると、該当のIssueをCloseします。 ここは手動でCloseします。 もし仮に誤って別のIssueをCloseしても、ステップ1.で再びReopenされるので大丈夫です。
まとめ
この運用が開始して、常に本番適用されているイメージに脆弱性があるかの判断が簡単になりました。 脆弱性のIssueが空になると、本番のイメージに脆弱性が含まれないと安心できます。
YakumoはCybozu内で初めてのKubernetes本番運用ということで、クラウドネイティブ時代の開発・運用の仕組みも同時に作っています。 その1つの取り組みである、コンテナの脆弱性対応について紹介しました。 今後もサービスへ組み込んだ技術やノウハウがあれば、記事を書きたいと思います。