DevSecOps という言葉を聞いたことはあるでしょうか? この記事では、CircleCI を使ってコンテナのビルドを自動化できたら、レジストリに登録する前にSnykを使ってセキュリティスキャンを行う方法をご紹介します。また、脆弱性検出時の修正方法についてもご紹介します。

脆弱性検出を行うタイミング〜コード追加・修正時と定期実行

ソフトウェア開発において、関数に対してある引数を与えた際に、想定する結果が得られるかを確認するようなテストをユニットテストや単体テストと呼びます。追加した機能が正しく動作するかの検証や、機能の追加時に他の機能が以前と違った振る舞いをしていないか(新たにバグを組み込んでいないか)を確認することができます。

ここでは、仕様が変更されない限りは、あるテストに対する結果は常に同じであることが期待されます。その一方で、OSやコンパイラのバージョン、使用するライブラリのバージョンの変更に伴い、コードが全く変更されていない場合であっても、期待されたテスト結果が得られないことがあります。

脆弱性検出では、想定しない事態が(とりわけセキュリティに関連して)意図せず発生しないかを診断します。例えば、そもそもコードが脆弱性を内包しており(たとえば、SQLインジェクションを引き起こすような誤った文字列操作など)、適切な権限を持たない攻撃者が不正な方法で情報を取得したり、システムをダウンさせるような問題を検出します。

また、何も問題が検出されなかった場合であっても、先ほどと同様、ライブラリなどの環境の変更に伴い、新たに脆弱性が生じることもあれば、不正にアクセスする方法が新たに発見されることにより、これまでは顕在化していなかった問題が、世の中に広く知られる(その結果、システムがセキュリティ上の危機に晒される)こともあります。

人間ドックと同様に、これまで3年間、何の問題もなかったからといって、それが次の1年間、健康であることを保証するわけではありません(むしろ、そのような予断を持つことが病気の早期発見の妨げになり得ます)。

したがって、脆弱性の検出は(病気と同様、あらかじめ問題が発生するタイミングを予測できないことから)、

  • コードの追加・修正時
  • 一定期間ごと

に、

  • 気になるのであればそのたびに、というのではなく
  • 常に「必ず」実行する

ことで、アプリケーションやサービスの健康を保つことが可能になります。

コードの追加・修正時に脆弱性検出

それでは、node.js + React で書かれた Baby Hippo Gram (子カバグラム) サイトの構築を例に取って説明していきましょう(リポジトリ: https://github.com/mayoct/cci-hippo-container)。

circleci-hippo-container

CircleCI を使って

  • ビルド(依存関係のあるライブラリのインストール)
  • テスト
  • リリース(コンテナイメージをビルドし、リポジトリにプッシュ)

するための設定ファイルでは、ワークフローを次のように設定します。

hippo ワークフロー

hippo_workflow ワークフローでは、build ジョブを実行する際に、コンテキストとして docker を指定しています。ここでは、circleci/docker Orbを使うことで、アカウントの確認(20行目)、コンテナのビルド(21-3行目)、イメージのプッシュ(24-5行目)において、例えば、dockerhub のIDやパスワードを直接指定しなくても、circleci/docker Orbが、環境変数 DOCKER_LOGIN や DOCKER_PASSWORD の値を参照しており、docker コンテキストに組織で共用可能な値として、これらの環境変数の値を格納しているのです。

ここでビルドしたコンテナイメージに対して、今回は Snykを使って脆弱性のチェックを行い、問題がなければ、dockerhub にプッシュするようにしましょう。

イメージスキャンの例

snyk/snyk Orb を使用しています(20行目)。

コンテナイメージに対するスキャン(脆弱性の検出)は、snyk Orb の scan コマンドにイメージ名を渡すことで実行可能です(26~7行目)。

実際に Snyk を使用するにあたっては、Snyk の API Token を指定する必要があるのですが、(先ほどの dockerhub のアカウント情報と同様に)snyk Orb が参照する環境変数 SNYK_TOKEN を CircleCI の snyk-secrets コンテクストで設定しています。

合計 4行 の追加により、コンテナイメージのスキャンを組み込むことができました。これにより、コンテナイメージのビルドまで成功すれば、常にスキャンが実行され、脆弱性などの問題がない時に限り、コンテナレジストリのイメージが更新されます。

一定期間ごとに脆弱性検出

先にご紹介したような ビルド→テスト→脆弱性検出→リリースといった流れのワークフローを含むパイプライン(config.yml)ができていれば、コードの追加や修正を起点とするだけではなく、一定期間ごとに脆弱性の検出を行うことは容易です。

一定期間ごとにパイプラインを実行するには、パイプラインのスケジュール実行の機能を使用します。スケジュール実行は、パイプラインの設定(config.yml)の中に記述するのではなく、CircleCIのAPI、またはプロジェクト設定画面から設定します。

パイプラインのスケジュール

定期実行というと思い浮かぶ crontab を使った場合の設定方法とは異なります。スケジュール実行が特定のタイミングに集中することがないように、「いつ」実行されるのかを指定するのではなく、「どのくらいの間隔で」実行するのかを指定するようになっています。詳しくはドキュメントをご参照ください。

こちらに紹介しているのが、パイプラインのスケジュール実行とSlackによる結果通知を組み合わせた例です。ここではコードの追加・変更は発生していないにもかかわらず、10月19日に自動実行し成功していたジョブが、1週間後の10月26日の自動実行時には新たな脆弱性が検出された結果、失敗してしまいました。

失敗したビルドの例

何もしていない(コードを追加・修正していない)場合であっても、アプリケーションやサービスが動き続けているのであれば、セキュリティに関する対応が常に必要であることを示す一例と言えるかと思います。

さいごに

以前、「テスト分割と並列実行 - 実行時間を短縮する」というブログを書かせていただきましたが、CI/CDによる自動化や、CircleCIがもつテスト分割、並列実行により、ビルド〜テスト〜リリース〜デプロイにかかる時間を大幅に短縮することができます。 その短縮した時間を原資として、セキュリティに関する取り組みや品質の向上に取り組むことが重要です。

そもそも、自動化を導入することで、コードの開発のような自動化でカバーしきれない領域、運用のように非正常系は人のノウハウとツールの活用が重要な領域に、人間という貴重な(かつ、単純に頭数を増やすだけでは意味がない)リソースをどのように投下できるのかが、ソフトウェアの品質や、ビジネスを通じての提供価値の向上の上で、重要な鍵となります。

CircleCI にはビルドやリリース、リリースやデプロイ、セキュリティの質を向上するさまざまなツールを自動化ワークフローの中に簡単に取り込むための仕組みである Orb があります。CircleCIで、エンジニアが創造的な仕事に集中できる環境を整備し、ソフトウェア開発の質と量の双方を高めていきましょう。

関連記事