Checkovを使ってIaCコードの静的解析をしてみよう

# はじめに

みなさん、こんにちは。今回は Checkov というオープンソースを利用した IaC コードの静的解析 (SAST、Static Application Security Testing) についてのお話です。

IaC コードを開発していると、動作確認のために軽い気持ちで実際の環境へデプロイしてみる、といったことをついついやってしまうこともあるかと思います。もちろんそのケースで言えば動作確認ができたらすぐに削除されるため、たとえその環境に致命的なセキュリティ上の問題が含まれていたとしてもトラブルに発展する可能性は低いとは思います。ただ、なかにはウッカリ削除するのを忘れてしまって、、、とヒヤリハットを経験をされた方もいるのではないでしょうか。そんなヒヤリハットの予防策として、デプロイ前に致命的なセキュリティ上の問題が含まれていることに気づくことができるように、今回紹介する Checkov のような静的解析ツールの活用を検討いただくといいのかなと思います。

と前置きが長くなりましたが、本記事では IaC コードの静的解析ツール Checkov を実行できる環境をローカルに作るところからはじめて、簡素なサンプルを用いた静的解析の手動実行、静的解析を組み込んだシンプルなパイプラインの構築まで紹介していきたいと思います。

# Checkovとは

Checkov とは、セキュリティやコンプライアンスの問題につながる可能性のある構成ミスはないか、といった観点で Infrastructure as Code (IaC) コードのスキャンを行う Python 製の静的解析ツールです。

Checkov には、一般的な構成ミスの問題をチェックするための事前定義や、Center for Internet Security (CIS) や Amazon Web Services (AWS) Foundations Benchmark などの一般的な業界標準への準拠をチェックするための事前定義が含まれているため、このツールを活用することで実際のクラウド環境にデプロイする前にセキュリティやコンプライアンスの問題を簡単に見つけることができるようになります。

なお、今回の記事では Terraform コードを対象としておりますが、CloudFormation や Azure Resource Manager(ARM) 、Kubernetes マニフェストに Dockerfile などのさまざまな IaC ツールにも対応しています。

https://www.checkov.io/1.Welcome/What%20is%20Checkov.html

# Checkovを使ってみよう

「はじめに」ですでに述べたとおり、今回はローカルにCheckovを実行できる環境を作るところからはじめて、簡素なサンプルを用いた静的解析の手動実行、静的解析を組み込んだシンプルなパイプラインの実装まで紹介していきたいと思います。

## まずは開発環境の構築から

今回は AWS CloudShell 上に開発環境を作っていきたいと思います。それではまず Checkov をインストールする事前準備として Python をインストールしましょう。

```bash
$ sudo yum install -y python3
$ python3 --version
Python 3.9.16
```

次に、[Checkov公式ドキュメント] を参考に Checkov をインストールします。

[Checkov公式ドキュメント]: https://www.checkov.io/2.Basics/Installing%20Checkov.html

```bash
$ pip3 install --upgrade pip && pip3 install --upgrade setuptools
$ pip3 install checkov
$ checkov --version
3.1.26
```

さらに、[Terraform公式ドキュメント] を参考に Terraform をインストールします。

[Terraform公式ドキュメント]: https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli

```bash
$ sudo yum install -y yum-utils
$ sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo
$ sudo yum -y install terraform
$ terraform --version
Terraform v1.6.5
on linux_amd64
```

最後に、AWS CLI の設定をして開発環境の構築は完了です。

```bash
$ aws configure
```

## 手動でテストを実行してみよう

では簡単なサンプルを用いて Checkov を使ったテストを行っていきましょう。

### Step1. テスト対象のIaCコード作成

今回は AWS上に簡単なネットワークリソースを作るだけのシンプルなコードを用意しました。

```tf:main.tf
terraform {
  required_version = "~> 1.6"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.29"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
  default_tags {
    tags = {
      Project     = "matt"
      Environment = "dev"
      Terraform   = "true"
    }
  }
}

locals {
  prefix = "matt-dev-"
}

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.2"

  name                   = "${local.prefix}vpc"
  cidr                   = "10.0.0.0/16"
  azs                    = ["ap-northeast-1c", "ap-northeast-1d"]
  public_subnets         = ["10.0.0.0/24", "10.0.4.0/24"]
  private_subnets        = ["10.0.128.0/24", "10.0.132.0/24"]
  enable_nat_gateway     = true
  single_nat_gateway     = true
  one_nat_gateway_per_az = false
}
```

### Step2. テストの手動実行

Checkov には Terraform を対象とした静的解析の方法として、Terraform コード自体を静的解析する方法と、Terraform plan の結果（≒実際に作成するリソース）を静的解析する方法の 2 パターン用意されています。チェックする観点がやや違うので、今回は両方をまとめて実行していきたいと思います。

- [Terraform Plan Scanning](https://www.checkov.io/7.Scan%20Examples/Terraform%20Plan%20Scanning.html)
- [Terraform Scanning](https://www.checkov.io/7.Scan%20Examples/Terraform.html)

それでは、今回のサンプルコードでは外部モジュールを利用しているため、外部モジュールのコードも静的解析を対象とするように `--download-external-modules true` オプションも指定して、実行していきましょう。

```txt
$ terraform init
$ terraform plan -o tfplan.binary
$ terraform show -json tfplan.binary | jq > tfplan.json
$ checkov -d . --download-external-modules true

       _               _              
   ___| |__   ___  ___| | _______   __
  / __| '_ \ / _ \/ __| |/ / _ \ \ / /
 | (__| | | |  __/ (__|   < (_) \ V / 
  \___|_| |_|\___|\___|_|\_\___/ \_/  
                                      
By Prisma Cloud | version: 3.1.26 

terraform scan results:

Passed checks: 69, Failed checks: 6, Skipped checks: 0

Check: CKV_AWS_130: "Ensure VPC subnets do not assign public IP by default"
        PASSED for resource: module.vpc.aws_subnet.public
        File: /.external_modules/github.com/terraform-aws-modules/terraform-aws-vpc/v5.2.0/main.tf:97-124
        Calling File: /main.tf:27-39
        Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-networking-policies/ensure-vpc-subnets-do-not-assign-public-ip-by-default

~ 省略 ~

Check: CKV_TF_1: "Ensure Terraform module sources use a commit hash"
        FAILED for resource: vpc
        File: /main.tf:27-39
        Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/supply-chain-policies/terraform-policies/ensure-terraform-module-sources-use-git-url-with-commit-hash-revision

                27 | module "vpc" {
                28 |   source  = "terraform-aws-modules/vpc/aws"
                29 |   version = "~> 5.2"
                30 | 
                31 |   name                   = "${local.prefix}vpc"
                32 |   cidr                   = "10.0.0.0/16"
                33 |   azs                    = ["ap-northeast-1c", "ap-northeast-1d"]
                34 |   public_subnets         = ["10.0.0.0/24", "10.0.4.0/24"]
                35 |   private_subnets        = ["10.0.128.0/24", "10.0.132.0/24"]
                36 |   enable_nat_gateway     = true
                37 |   single_nat_gateway     = true
                38 |   one_nat_gateway_per_az = false
                39 | }

~ 省略 ~

terraform_plan scan results:

Passed checks: 12, Failed checks: 3, Skipped checks: 0

Check: CKV_AWS_130: "Ensure VPC subnets do not assign public IP by default"
        PASSED for resource: module.vpc.aws_subnet.private[0]
        File: /tfplan.json:407-431
        Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-networking-policies/ensure-vpc-subnets-do-not-assign-public-ip-by-default

~ 省略 ~

Check: CKV2_AWS_11: "Ensure VPC flow logging is enabled in all VPCs"
        FAILED for resource: module.vpc.aws_vpc.this[0]
        File: /tfplan.json:559-578
        Guide: https://docs.prismacloud.io/en/enterprise-edition/policy-reference/aws-policies/aws-logging-policies/logging-9-enable-vpc-flow-logging

                560 |               "values": {
                561 |                 "assign_generated_ipv6_cidr_block": null,
                562 |                 "cidr_block": "10.0.0.0/16",
                563 |                 "enable_dns_hostnames": true,
                564 |                 "enable_dns_support": true,
                565 |                 "instance_tenancy": "default",
                566 |                 "ipv4_ipam_pool_id": null,
                567 |                 "ipv4_netmask_length": null,
                568 |                 "ipv6_ipam_pool_id": null,
                569 |                 "ipv6_netmask_length": null,
                570 |                 "tags": {
                571 |                   "Name": "matt-dev-vpc"
                572 |                 },
                573 |                 "tags_all": {
                574 |                   "Environment": "dev",
                575 |                   "Name": "matt-dev-vpc",
                576 |                   "Project": "matt",
                577 |                   "Terraform": "true"
                578 |                 }

```

出力例では Terraform コードについては 69 項目チェックして問題が 6 件検知、Terraform plan 結果については 12 項目チェックして問題が 3 件検知されたことがわかります。

なお、checkovコマンドの出力結果からは問題の重大度がわからないのですが、各チェック項目ごとに参照先ガイドの URL が記載されており、こちらのリンク先から次の出力例のように問題の詳細やその問題の重大度(Severity)を確認することができます。

![checkov-detail.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613908/5bc3568e-e0fd-0c1d-72a8-56e84474c23e.png)

以上、ローカル環境で Checkov の静的解析を実行する方法のご紹介でした。

## CI/CDパイプラインに組み込んでみよう

次はもう一歩進んで、ソースコードの更新をトリガーに自動でテストを実行する CI/CD パイプラインを構築し、実際に動かすところまで行ってみたいと思います。今回の例では CI/CD パイプラインとして GitLab CI/CD を使っていきます。

### Step1. パイプライン定義の作成

まずは GitLab CI/CD パイプラインの定義ファイル `.gitlab-ci.yml` を作成していきましょう。今回の例では簡略化のためにテストステージだけを定義し、GitLab の画面からテスト結果を確認しやすいように JUnit 形式で出力しています。

```yml:.gitlab-ci.yml
stages:
    - test

checkov:
  stage: test
  allow_failure: true  # True for AutoDevOps compatibility
  image:
    name: bridgecrew/checkov:latest
    entrypoint:
      - '/usr/bin/env'
      - 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
  rules:
    - if: $SAST_DISABLED
      when: never
    - if: $CI_COMMIT_BRANCH
      exists:
        - '**/*.yml'
        - '**/*.yaml'
        - '**/*.json'
        - '**/*.template'
        - '**/*.tf'      
        - '**/serverless.yml'
        - '**/serverless.yaml'
  script:
    - checkov -d . --download-external-modules true -o junitxml | tee checkov.test.xml
  artifacts:
    reports:
      junit: "checkov.test.xml"
    paths:
      - "checkov.test.xml"
```

### Step2. パイプラインの動作確認

先ほど作成したパイプライン定義 `.gitlab-ci.yml` をリモートリポジトリへ push すると自動的にパイプラインが実行されますので、GitLabの画面からテスト結果を確認してみましょう。

出力例のように Build > Pipelines > 対象パイプライン > Testタブ と遷移させることでテスト結果のサマリーを確認することができます。

![checkov-summary.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613908/bd986077-8e1c-6e28-f0ef-0c7bd7959bfe.jpeg)

また、テストのサマリー画面にてジョブ名(例ではcheckov)をクリックすることで、テスト結果の一覧を表示させることができますし、この一覧画面で「View detauls」をクリックすることでより詳細な情報を得ることもできます。

![checkov-list.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/613908/fd0cabe6-ece7-e6bd-2c29-d0b506946e81.jpeg)

本来はパイプラインにビルドステージやデプロイステージを持たせたりすると思いますが、ひとまずこれでソースコードのプッシュを契機にテストを走らせる、という最低限の流れを自動化することができました。

# 終わりに

ということで、Checkov はいかがだったでしょうか？検出された問題の重大度が出力結果からパッとわからない点は正直やや微妙かもしれませんが、とても簡単に静的解析を導入することができたかと思います。

IaC はしているけど静的解析は何もしてないよ、という方はデプロイ前に致命的なセキュリティ上の問題が含まれていることに気づくことができるように、ぜひ今回紹介する Checkov のような静的解析ツールの活用を検討いただくといいのかなと思います。

以上、IaC コードの静的解析を実現する「Checkov」のご紹介でした。

---

- AWS は、米国その他の諸国における Amazon.com, Inc. またはその関連会社の商標です。
- Terraform は、HashiCorp, Inc. の米国およびその他の国における商標または登録商標です。
- その他、記載されている会社名および商品・製品・サービス名は、各社の商標または登録商標です。