checkovをreviewdogに対応させた話

タイトルの通りですが`checkov`は出力結果が`reviewdog`の入力形式に対応していないので、そのままだと組み合わせて使うことができません。そこで以下のようにスクリプトを書いて対応しました。

## やりたい事

terraformを管理するGithubのリポジトリでPRを作成した際にterraformコードの静的解析を行い、問題があればPRにコメントを投稿してくれる、という形を目指します。

ここでは`checkov`で静的解析の結果を標準出力し`reviewdog`がパイプで結果を受け取ってPRにコメントします。

実際には以下のような表示になります。

![スクリーンショット 2021-03-31 20.37.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206595/2073fc93-2e76-d205-d59a-e3328282d0f2.png)


## 各ツールについて

- [checkov]("https://github.com/bridgecrewio/checkov")
- [reviewdog]("https://github.com/reviewdog/reviewdog")

## 実装

まず`reviewdog`が受け取り可能な形式については以下に記載があります。

https://github.com/reviewdog/reviewdog#input-format

そのなかで今回は`errorformat`という形式に合わせていこうと思います。`errorformat`は行単位で指定した形式の標準出力を受け取ることができます。

例えば標準出力が`{file}:{line number}:{column number}: {message}`のような形式だとすると、`errorformat`は`%f:%l:%c: %m`となります。

```bash
$ golint ./...
comment_iowriter.go:11:6: exported type CommentWriter should have comment or be unexported
$ golint ./... | reviewdog -efm="%f:%l:%c: %m" -diff="git diff FETCH_HEAD"
```

### 出力形式の変換

`reviewdog`でどのように受け取るかが分かったので次は`checkov`の出力を調整します。

`checkov`の出力形式は以下に記されている通りです。

https://github.com/bridgecrewio/checkov/blob/master/docs/1.Introduction/Results.md

これらの出力形式からどれかを選び`reviewdog`の受け取り形式に合うよう変更していきます。選択可能なかで形式変更が最も容易なのはJSONなので、一旦JSON形式で出力した結果をパイプで受け取り、自作のスクリプトを通して`errorformat`に変換していきます。

コマンドとしては以下のようになります。

```bash
$ checkov -d . -o json | python3 parse.py
```

ここでは`python`を利用していますが言語は何でも構いません。

`python`のコードは以下のようになります。
（結構雑に書いているので細かいバグがありそうですが...）

#### parse.py

```python
import json, sys

def main():
    data = json.load(sys.stdin)
    failed_checks = data["results"].get("failed_checks")
    if failed_checks is None:
        exit(0)

    for failed_check in failed_checks:
        file_name = failed_check["file_path"].replace('/', '')
        line_number = failed_check["file_line_range"][0]
        error_message = failed_check["check_name"]
        print('{}:{}: {}'.format(file_name, line_number, error_message))

    exit(1)


if __name__ == "__main__":
    main()
```

## テスト実行

ここまで出来たらローカル環境で実際にtfファイルを作成してテストしてみます。

#### gke.tf

```hcl
resource "google_service_account" "default" {
  account_id   = "service-account-id"
  display_name = "Service Account"
  project      = var.project_id
}

resource "google_container_cluster" "primary" {
  name     = "my-gke-cluster"
  location = var.region

  # We can't create a cluster with no node pool defined, but we want to only use
  # separately managed node pools. So we create the smallest possible default
  # node pool and immediately delete it.
  remove_default_node_pool = true
  initial_node_count       = 1
}

resource "google_container_node_pool" "primary_preemptible_nodes" {
  name       = "my-node-pool"
  location   = var.region
  cluster    = google_container_cluster.primary.name
  node_count = 1

  node_config {
    preemptible  = true
    machine_type = "e2-medium"

    # Google recommends custom service accounts that have cloud-platform scope and permissions granted via IAM Roles.
    service_account = google_service_account.default.email
    oauth_scopes = [
      "https://www.googleapis.com/auth/cloud-platform"
    ]
  }
}
```

ローカルでテスト実行すると以下のような表示になります。

```bash
$ checkov -d . -o json | python3 parse.py | reviewdog -efm="%f:%l: %m" -name="checkov" -reporter=local -filter-mode=nofilter
gke.tf:7: Ensure PodSecurityPolicy controller is enabled on the Kubernetes Engine Clusters
gke.tf:7: Ensure Kubernetes Cluster is created with Private cluster enabled
gke.tf:7: Ensure Kubernetes Cluster is created with Alias IP ranges enabled
gke.tf:7: Ensure master authorized networks is set to enabled in GKE clusters
gke.tf:7: Ensure Kubernetes Clusters are configured with Labels
gke.tf:7: Ensure a client certificate is used by clients to authenticate to Kubernetes Engine Clusters
gke.tf:7: Ensure GKE basic auth is disabled
gke.tf:7: Ensure Network Policy is enabled on Kubernetes Engine Clusters
gke.tf:18: Ensure 'Automatic node upgrade' is enabled for Kubernetes Clusters
gke.tf:18: Ensure Container-Optimized OS (cos) is used for Kubernetes Engine Clusters Node image
gke.tf:18: Ensure 'Automatic node repair' is enabled for Kubernetes Clusters
```

後は`Github Actions`のyamlに設定ファイルとして書いてあげればCIとして利用することが出来ます。

以上、「checkovをreviewdogに対応させた話」でした。