DockleやTrivyをCircle CIのDocker Orbと組み合わせてセキュアなコンテナを継続的に作るっ！

# 概要

[Circle CI](https://circleci.com/ja/)の「[circleci/docker](https://circleci.com/orbs/registry/orb/circleci/docker)」という[Orb](https://circleci.com/orbs/)を使って、以下を実践してみました！ʕ◔ϖ◔ʔ


- コンテナのビルド
- [Dockle](https://github.com/goodwithtech/dockle)と[Trivy](https://github.com/aquasecurity/trivy)を使って、ビルドしたコンテナイメージをチェック
  - OSや依存パッケージが脆弱性を含んでいないかをチェック
  - [ベストプラクティス](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/)に準拠したセキュアなコンテナになっているかをチェック
- 上記チェック結果が`Pass`なら、`docker hub`にコンテナイメージを登録

上述のサンプルを紹介しつつ、設定ファイル`.circleci/config.yml`を書く際のポイントなどを説明します。この記事を読み終わった時に、あなたにとってセキュアなコンテナのビルドがより身近なものになっていたら嬉しいなぁと思います！

ゆっくり見ていってね！(o´・з・)o

# 読んでほしい人

- `Dockle`や`Trivy`といったコンテナチェックツールに興味がある方
- CIでのコンテナチェックに興味がある方
- CircleCIのOrbという仕組みに興味がある方
- CircleCIを含むCI/CDに知見がある方（マサカリ、お願いします！m(_ _)m）

etc..

# 作ったもの

まずは、実践した内容の雰囲気を掴んでいただくため、実際に作ったサンプルをごらんください。٩(ˊᗜˋ*)و

## Dockle on CircleCI

- GitHubリポジトリ
  - [kemokemo/try-dockle](https://github.com/kemokemo/try-dockle)
- CircleCIでのビルド結果
  - <img width="1390" alt="スクリーンショット 2020-02-09 12.57.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166225/bd8de425-6dbf-ee56-5857-019f073506c9.png">
      - [try-dockle - Pipelines](https://app.circleci.com/github/kemokemo/try-dockle/pipelines)
  - <img width="1118" alt="スクリーンショット 2020-02-10 8.35.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166225/f983d7d9-6e89-5030-f2b5-a4f21bced917.png">
      - CIのジョブでDockleを実行した結果のサンプル画像です。`ERROR`や`WARN`無いよ！
- Docker Hubに登録したコンテナイメージ
  - <img width="1287" alt="スクリーンショット 2020-02-09 13.03.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166225/64aff177-12d5-a36b-46fe-252079c0f4ae.png">
  - [kemokemo/try-dockle](https://hub.docker.com/repository/docker/kemokemo/try-dockle)

## Trivy on CircleCI

- GitHubリポジトリ
  - [kemokemo/try-trivy](https://github.com/kemokemo/try-trivy)
- CircleCIビルド結果
  - <img width="1408" alt="スクリーンショット 2020-02-10 8.50.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166225/17686de3-7a67-3ba9-4980-d52a3f36a3dd.png">
  - [try-trivy - Pipelines](https://app.circleci.com/github/kemokemo/try-trivy/pipelines)
  - <img width="1122" alt="スクリーンショット 2020-02-10 8.35.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166225/c9c07b99-3d90-e522-1d05-5d449a28435b.png">
      - CircleCIのジョブでTrivyを実行したサンプル画像です。脆弱性無いよ！
- Docker Hubに登録したコンテナイメージ
  - [kemokemo/try-trivy](https://hub.docker.com/repository/docker/kemokemo/try-trivy)

# 準備

## ログイン

まずは`CircleCI`や`docker hub`にログインできるようにしましょう。

- [ログイン - CircleCI](https://circleci.com/ja/vcs-authorize/)
- [Sign up for Docker Hub](https://hub.docker.com/signup)

## リントツールを使って手元でチェック

ケモケモ「よっしゃ出来た！CircleCIさん、がっつりビルド頼むわ！！」
CircleCI「記法がinvalidやで。Failやで。」
<img width="1097" alt="スクリーンショット 2020-02-20 20.45.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166225/2c9921f2-1fd3-bc98-0a19-45a3f44c7d40.png">
ケモケモ「Oh... (´・ω・｀)」

せっかく書いた`.circleci/config.yml`が、記法の間違いなどビルドできないと辛いですね。そういった問題を未然に防ぐため、コミットする前に`circleci`ツールでチェックしちゃいましょうヽ(=´▽`=)ﾉ。

まずはインストール！

- [CircleCI のローカル CLI の使用 - インストール](https://circleci.com/docs/ja/2.0/local-cli/#%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)

以下のコマンドで`.circleci/config.yml`が動きそうかチェックします。

```sh
circleci config validate
```

詳しくは「[CircleCI のコンフィグのバリデーション](https://circleci.com/docs/ja/2.0/local-cli/#circleci-%E3%81%AE%E3%82%B3%E3%83%B3%E3%83%95%E3%82%A3%E3%82%B0%E3%81%AE%E3%83%90%E3%83%AA%E3%83%87%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3)」をご覧ください。

## `Dockle`をインストール

CIで継続的に実行するのが本記事の目的ではありますが、便利なツールですし手元でコミット前にチェックするのは良い習慣ですのでインストールしましょう。

公式の [Installation](https://github.com/goodwithtech/dockle#installation) で各種プラットフォーム向けのインストール方法が書かれているので、好きな方法でインストールします。

使い方も簡単で、チェックしたいコンテナイメージの名前を指定して実行するだけです。めちゃシンプル！

```sh
$ dockle {YOUR_IMAGE_NAME}
```

チェックしてくれるポイントは [Checkpoint Summary](https://github.com/goodwithtech/dockle#checkpoint-summary) にまとまってます。

## `Trivy`をインストール

こちらもやはり手元で実行できるようにインストールしましょう。先の`Dockle`同様にGo言語製のツールですので、非常に多くのプラットフォームで実行可能です。

公式の [Installation](https://github.com/aquasecurity/trivy#installation) を見ながらインストールします。
簡単に使えます。シンプルです。

```sh
$ trivy {YOUR_IMAGE_NAME}
```

実行結果のサンプルが [Examples](https://github.com/aquasecurity/trivy#examples) に多数掲載されているのでチェキです。:eyes:

# `Orb`選び

この記事のサブテーマとしてCircle CIのOrbを積極的に使います。まずは、目的にあったOrbを [CircleCI Orb Registry](https://circleci.com/orbs/registry/) で探してみましょう。

<img width="971" alt="スクリーンショット 2020-02-20 23.10.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166225/01f2e9ab-508d-c8bb-2db8-8b4c254feb34.png">

今回はコンテナのビルドとプッシュをしたいので`docker`で検索してみました（上図）。`CERTIFIED`マークの`circleci/docker`を使ってみましょう。

## Orb: `circleci/docker`

https://circleci.com/orbs/registry/orb/circleci/docker
<img width="1170" alt="スクリーンショット 2020-02-20 23.13.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166225/ea216ed7-abfc-5918-fd6a-a08bb25df515.png">

Orbの説明ページでは、Orbの機能を呼び出すための方法について以下の情報が記載されています。

- 冒頭のクイックスタートガイドで、`.circleci/config.yml`にどう書けば使い始められるか説明してる
- 左のカラムに並ぶ`Usage Examples`で、需要が高そうな使い方に対してjobやworkflowの書き方を実例と共に説明してる
- 左のカラムに並ぶ`Jobs`で、job名やパラメータの指定方法を説明してる
- 左のカラムに並ぶ`Commands`で、command名やパラメータの指定方法を説明してる

### クイックスタートガイド

以下を書くだけでOrbの力を使えるようになります。パネェ₍₍ (ง ˙ω˙)ว ⁾⁾

```yml
version: 2.1

orbs:
  docker: circleci/docker@0.5.20
```

### Usage Examples

比較的シンプルな、以下のサンプルが参考になります。

- [standard-build-and-push](https://circleci.com/orbs/registry/orb/circleci/docker#usage-standard-build-and-push)
    - Orbの定義済みのjobである`publish`を使ったサンプル。
    - コンテナのbuildとpushをまとめて実施してくれる。便利。
- [build-push-digest](https://circleci.com/orbs/registry/orb/circleci/docker#usage-build-push-digest)
    - Orbの定義済みのcommandである`check`や`build`を使ったサンプル。
    - `docker.hub`へのログインチェックや、build、pushなど、順を追って実施したい時に参考になる。

今回は、上記`build-push-digest`をベースに作っていきます。

### Executor

Orbの実態は、CI/CDにすぐ組み込んで便利に使えるようCircleCIやパートナー企業が作ってくれたカスタムコンテナイメージです。

では、この`circleci/docker`のベースイメージはどんなものなのでしょう？今回のサンプルで使う「[Executor - docker](https://circleci.com/orbs/registry/orb/circleci/docker#executors-docker)」でSourceを確認することでわかります。

<img width="1141" alt="スクリーンショット 2020-02-21 0.15.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166225/87afd6b4-a527-b95d-a11a-fedbae381c6a.png">

「[ circleci/python ](https://hub.docker.com/r/circleci/python)」を使っているようです。このイメージを作る`Dockerfile`は「[ CircleCI-Public / circleci-dockerfiles ](https://github.com/CircleCI-Public/circleci-dockerfiles/blob/master/python/images/3.6.10/Dockerfile)」にあります。追加で依存パッケージなどを使いたい場合、コンテナの素性が関係してくる場合があるので心の片隅に置いておきます。

# 書き始めよう

かなり入念に、CircleCIの設定を書くための準備とOrbの機能を確認しました。そろそろ実際に`config.yml`を書き始めましょう。

## まずは`Orb`だけで書いてみる

まずは`Orb`の機能だけでCIの骨子を組み立てましょう。`Orb`の各種機能を呼び出す方法を整理すると、以下のような感じです。

- `executor`: {Orb名}/{OrbのExecutor名}
  - ex) `docker/docker`
  - [circleci/dockerのExecutors](https://circleci.com/orbs/registry/orb/circleci/docker#executors)
- `command`: {Orb名}/{OrbのCommand名}
  - ex) `docker/build`
  - [circleci/dockerのCommands](https://circleci.com/orbs/registry/orb/circleci/docker#commands)
- `job`: {Orb名}/{OrbのJob名}
  - ex) `docker/publish`
  - [circleci/dockerのJobs](https://circleci.com/orbs/registry/orb/circleci/docker#jobs)

上述のサンプルなどをベースにザクッと書いてみたのが以下です。

```yml
version: 2.1

orbs:
  docker: circleci/docker@0.5.20

jobs:
  # ビルドしたりテストしたりするジョブ。
  build-and-test:
    executor: docker/docker
    steps:
      - setup_remote_docker
      - checkout
      # コンテナレジストリにログイン可能かチェックします。
      - docker/check

      # コンテナをビルドします。
      - docker/build:
          image: ${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}

      # ! DockleとTrivyを使ったコンテナのtestを書く予定地です。

workflows:
  # ビルドしたりテストしたり、masterにマージされたらlatestコンテナを更新するワークフロー。
  build-and-deploy:
    jobs:
      # masterを含む全てのブランチで実行するジョブ。
      - build-and-test

      # masterでだけ実行されるジョブ。latestのコンテナイメージをレジストリに登録、更新する。
      - docker/publish:
          image: ${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}
          tag: 'latest'
          requires:
            - build-and-test
          filters:
            branches:
              only: master

  # gitのタグがついたらビルドとテストをして、タグ付きのコンテナをレジストリに登録するワークフロー。
  deploy-tags:
    jobs:
      - build-and-test:
          filters:
            branches:
              ignore: /.*/
            tags:
              only: /^v.*/
      - docker/publish:
          image: ${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}
          tag: ${CIRCLE_TAG}
          # 「build-and-test」のジョブが成功した時だけ、このジョブを実行するよ。
          requires:
            - build-and-test
          filters:
            branches:
              ignore: /.*/
            tags:
              only: /^v.*/
```

なお、commandの`docker/check`は、イメージをpushするコンテナレジストリ（標準でdocker hub）にログイン可能かをチェックしてくれます。ログイン情報は、あらかじめ環境変数としてCircle CIに登録しておきます。ジョブの実行結果のログでは、ちゃんと伏せ字になりますので安心です。詳しくは [プロジェクト内で環境変数を設定する](https://circleci.com/docs/ja/2.0/env-vars/#%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E5%86%85%E3%81%A7%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B) を参照してください。

## `Dockle`と`Trivy`をCIに組み込む

実は`Dockle`も`Trivy`も、どちらも公式でCircleCIの実行サンプルを公開しています。

- [trivy - CircleCI](https://github.com/aquasecurity/trivy#circleci)
- [dockle - CircleCI](https://github.com/goodwithtech/dockle#circleci)

これらのサンプルを上述のOrbベースのCI設定に組み込みます。その際、ツールのインストールを行う箇所がそのままでは使えません。`circleci/docker`のOrbとは、`executor`として使っているコンテナイメージのOSが異なるためです。それらの違いも踏まえて実際に動作することを確認した設定内容が以下です。

```yml
version: 2.1

orbs:
  docker: circleci/docker@0.5.20

jobs:
  build-and-test:
    executor: docker/docker
    steps:
      - setup_remote_docker
      - checkout
      - docker/check
      - docker/build:
          image: ${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}

      # （追加）Dockleをインストールしてスキャン実行！
      - run:
          name: Install dockle
          command: |
             VERSION=$(
               curl --silent "https://api.github.com/repos/goodwithtech/dockle/releases/latest" | \
               grep '"tag_name":' | \
               sed -E 's/.*"v([^"]+)".*/\1/'
             )
             wget https://github.com/goodwithtech/dockle/releases/download/v${VERSION}/dockle_${VERSION}_Linux-64bit.tar.gz
             tar zxvf dockle_${VERSION}_Linux-64bit.tar.gz
             sudo mv dockle /usr/local/bin
      - run:
          name: Scan the image with dockle
          command: dockle --exit-code 1 ${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}:${CIRCLE_SHA1}

      # （追加）Trivyをインストールしてスキャン実行！
      - run:
          name: Install trivy
          command: |
            VERSION=$(
                curl --silent "https://api.github.com/repos/aquasecurity/trivy/releases/latest" | \
                grep '"tag_name":' | \
                sed -E 's/.*"v([^"]+)".*/\1/'
            )
            wget https://github.com/aquasecurity/trivy/releases/download/v${VERSION}/trivy_${VERSION}_Linux-64bit.tar.gz
            tar zxvf trivy_${VERSION}_Linux-64bit.tar.gz
            sudo mv trivy /usr/local/bin
      - run:
          name: Scan the image with trivy
          command: trivy --exit-code 0 --no-progress ${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}:${CIRCLE_SHA1}

workflows:
  build-and-deploy:
    jobs:
      - build-and-test
（後略）
```

実際に動かしてみるとこんな感じになります。

<img width="1386" alt="スクリーンショット 2020-02-27 21.36.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/166225/8be3df79-325b-7b67-b84e-df4347295908.png">

良いですね、バッチリチェックできています。:tada:
`Dockle`と`Trivy`両方を使った`config.yml`の完成形は [こちら](https://github.com/kemokemo/web/blob/master/.circleci/config.yml) を御覧ください。

# まとめ

CIを使ったコンテナの作成とレジストリへの登録は、Kubernetesクラスタへのデプロイを想定したGitOpsなどで必須となる重要なポイントだと感じています。これらの技術をProduction環境へ適用することを考えるならば、使用するコンテナ自体の脆弱性チェックはもはや避けては通れない課題です。

今回は、極力余計なものは省いて「いかに楽して脆弱性チェックの機構をCIに取り込むか」という点を意識して実践したつもりです。本記事はCircle CIを使った例でしたが、`Dockle`と`Trivy`の公式ドキュメントにもあるように [Travis CI](https://travis-ci.com/) など多数のCI環境で同様の「コンテナ脆弱性チェック」の仕組みを実現することが可能です。

この記事が、コンテナのセキュリティを意識し始めた私のような方にとって、少しでも役立つものになっていることを願っていますʕ◔ϖ◔ʔ

# おまけ

Circle CI関連の資料ばかりなのですが、有益だなぁと思ったリンクを載っけときます(ΦωΦ)

- [セキュリティ機能](https://circleci.com/docs/ja/2.0/security/)
  - Circle CIのセキュリティに関する取り組みについて書いてあります。セキュリティを懸念する上司やチームメンバーに説明する際の資料として良さそう。

- [定義済み環境変数](https://circleci.com/docs/ja/2.0/env-vars/#%E5%AE%9A%E7%BE%A9%E6%B8%88%E3%81%BF%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0)
  - Circle CIのジョブで使える定義済みの環境変数。リポジトリ名やタグ名など、比較的よく使いそうな情報が多数揃ってる。