GitHub Actions + Trivy でDevSecOpsを実現する

本番環境で稼働中のDockerイメージの脆弱性チェックを定期的に行なっていますか？
デプロイ時に脆弱性チェックをする方法は[こちら](https://qiita.com/homines22/items/af96baa6ba51b1d59124)の記事に書いていますが、運用フェーズに入ってからのセキュリティ対策について書いていませんでした。むしろ長い運用フェーズの方が脆弱性が多く発見されるので定期的なチェックは必須です。しかし、定期的なチェックは面倒だしコストがかかります。
そこで、[GitHub Actions](https://github.co.jp/features/actions)と[Trivy](https://github.com/aquasecurity/trivy)を使って手軽に定期的に脆弱性スキャンが行える方法をご紹介します。ただスキャンするだけでは運用時には辛いので、脆弱性が発見されたらGitHubのIssueが作成されるようにします。そうすれば何に対処すればいいか各リポジトリごとに管理できますし、プルリクとIssueを関連づけてプルリクがマージされたらIssueもクローズみたいな運用をすれば対応済みかどうか分かりやすくなります。

# GitHub Actions

## Schedule
定期的にスキャンをするのでGitHub Actionsのschedule機能を使用します。schedule機能と言っても自体は`cron`です。例えば毎週月曜日の9:00(JST)にCIを回したいのであれば以下のようになります。
:warning: GitHub ActionsはUTCで動くので注意

```yaml
on:
  schedule:
    - cron: '0 0 * * 1'
```

## 必要なコマンドのインストール
Trivyはもちろんですが、Issueを作成する上で以下のコマンドのインストールをします。

|コマンド|説明|
|:--:|:--|
|Trivy|脆弱性スキャンコマンド|
|hub|Issue作成する|
|pandoc|Trivyのスキャン結果をHTMLにする|

```yaml
- name: Install commands 
  run: |
    sudo apt install apt-transport-https gnupg
    wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
    echo deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -cs) main | sudo tee -a /etc/apt/sources.list.d/trivy.list
    sudo apt update
    sudo apt install --no-install-recommends trivy

    sudo snap install hub --classic

    VERSION=$(curl --silent "https://api.github.com/repos/jgm/pandoc/releases/latest" | \
                    grep '"tag_name":' | \
                    sed -E 's/.*"([^"]+)".*/\1/')
    curl -L -o pandoc.deb https://github.com/jgm/pandoc/releases/download/${VERSION}/pandoc-${VERSION}-1-amd64.deb
    sudo dpkg -i pandoc.deb
```

## Dockerイメージのスキャン
Trivyでスキャンする時のオプションは[こちら](https://github.com/aquasecurity/trivy#examples)を参考にしてください。ここではseverity(重大度)をHIGH・CRITICALにして、脆弱性を発見したらexit codeを1にします。

```yaml
- name: Scan Image
  run: trivy -q --severity HIGH,CRITICAL --exit-code 1 ${IMAGE_NAME} > tmp.txt
```

## Issueの作成
脆弱性がなければ終了し、あればIssueをあげます。その前にIssueが見やすくなるようにTextベースのファイルをHTMLに変換します。`echo -e "Security Alert\n" > result.html`の`Security Alert`がIssueのタイトル、trivyのスキャン結果は説明欄に記載、`hub`コマンドの`-l`オプションで`security`ラベルがつくようになっています。
`GITHUB_TOKEN`と`GITHUB_PASSWORD`に関してですが、トークンだけあればパスワードは不要とドキュメントに書いてあったのですが認証エラーになったので両方書いてあります。中身の値はどちらも同じですが...

```yaml
- name: Create Issue
  if: failure()
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    GITHUB_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
  run: |
    pandoc tmp.txt -o tmp.html
    sed -i -e "s/<pre><code>//g" tmp.html
    sed -i -e "s/<\/code><\/pre>//g" tmp.html
    echo -e "Security Alert\n" > result.html
    cat tmp.html >> result.html
    sudo chown root:root /
    hub issue create -F result.html -l security
```

以下はテスト用にCIを走らせてみた結果です。

<img width="1019" alt="screenshot 2019-11-12 1.09.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173064/4d9d5af3-cac8-fe42-60d8-6d99b5f7e5e0.png">

いい感じですね :smile:

## 完成形
完成形のWorkflowを書いておきます。`IMAGE_NAME`と`GITHUB_USER`を自身の環境に合わせて変更すれば動作するはずです。

```yaml
name: Vulnerability scan

on:
  schedule:
    - cron: '0 0 * * 1'

env:
  IMAGE_NAME: alpine:3.10.1
  GITHUB_USER: homoluctus

jobs:
  scan:
    name: Scan images
    runs-on: ubuntu-18.04
    steps:
      - uses: actions/checkout@master
        with:
          ref: master

      - name: Install commands
        run: |
          sudo apt install apt-transport-https gnupg
          wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
          echo deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -cs) main | sudo tee -a /etc/apt/sources.list.d/trivy.list
          sudo apt update
          sudo apt install --no-install-recommends trivy
          sudo snap install hub --classic

          VERSION=$(curl --silent "https://api.github.com/repos/jgm/pandoc/releases/latest" | \
                    grep '"tag_name":' | \
                    sed -E 's/.*"([^"]+)".*/\1/')
          curl -L -o pandoc.deb https://github.com/jgm/pandoc/releases/download/${VERSION}/pandoc-${VERSION}-1-amd64.deb
          sudo dpkg -i pandoc.deb

      - name: Pull images
        run: docker pull ${IMAGE_NAME}

      - name: Scan Image
        run: trivy -q --severity HIGH,CRITICAL --exit-code 1 ${IMAGE_NAME} > tmp.txt

      - name: Create Issue
        if: failure()
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITHUB_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
        run: |
          pandoc tmp.txt -o tmp.html
          sed -i -e "s/<pre><code>//g" tmp.html
          sed -i -e "s/<\/code><\/pre>//g" tmp.html
          echo -e "Security Alert\n" > result.html
          cat tmp.html >> result.html
          sudo chown root:root /
          hub issue create -F result.html -l security

      - name: Notify Result to Slack
        uses: homoluctus/slatify@master
        if: always()
        with:
          type: ${{ job.status }}
          channel: '#general'
          job_name: ':sniper: *Vulnerability Scan*'
          url: ${{ secrets.SLACK_WEBHOOK }}
```

これで定期的な脆弱性スキャンができるようになりました。
GitHub Actionsと様々なツールを組み合わせればDevOps/DevSecOpsが簡単に実現できますので、試してみる価値はあると思います。
余裕のある方はGitHubの`dependabot`、Dockerfileのリンター・スキャナである`dockle`や有料ツールが豊富ですので、多角的に脆弱性チェックをするのもありでしょう。