タイトルの通りですがcheckov
は出力結果がreviewdog
の入力形式に対応していないので、そのままだと組み合わせて使うことができません。そこで以下のようにスクリプトを書いて対応しました。
やりたい事
terraformを管理するGithubのリポジトリでPRを作成した際にterraformコードの静的解析を行い、問題があればPRにコメントを投稿してくれる、という形を目指します。
ここではcheckov
で静的解析の結果を標準出力しreviewdog
がパイプで結果を受け取ってPRにコメントします。
実際には以下のような表示になります。
各ツールについて
実装
まずreviewdog
が受け取り可能な形式については以下に記載があります。
そのなかで今回はerrorformat
という形式に合わせていこうと思います。errorformat
は行単位で指定した形式の標準出力を受け取ることができます。
例えば標準出力が{file}:{line number}:{column number}: {message}
のような形式だとすると、errorformat
は%f:%l:%c: %m
となります。
$ 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
の出力形式は以下に記されている通りです。
これらの出力形式からどれかを選びreviewdog
の受け取り形式に合うよう変更していきます。選択可能なかで形式変更が最も容易なのはJSONなので、一旦JSON形式で出力した結果をパイプで受け取り、自作のスクリプトを通してerrorformat
に変換していきます。
コマンドとしては以下のようになります。
$ checkov -d . -o json | python3 parse.py
ここではpython
を利用していますが言語は何でも構いません。
python
のコードは以下のようになります。
(結構雑に書いているので細かいバグがありそうですが...)
parse.py
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
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"
]
}
}
ローカルでテスト実行すると以下のような表示になります。
$ 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に対応させた話」でした。