😊

Trivy と Checkov を使って .tf ファイルをスキャンした結果の比較と所感

2023/06/05に公開

はじめに

こんにちは、クラウドエースのシステム開発部/SREディビジョン所属の菊池と申します。

本記事では、インフラストラクチャのコード管理に Terraform を使用している場合に、セキュリティ上の問題を検出するためのスキャンツールとして、TrivyCheckov を紹介します。
Trivy と Checkovは、どちらも OSS であり、コンテナイメージの脆弱性や IaC のミスコンフィギュレーションなどセキュリティに関する問題を検出するために使用されるツールです。

本記事では、IaC を用いたインフラストラクチャのセキュリティに関する課題、Trivy と Checkov を使って Terraform コードをスキャンする方法、検出された問題、および筆者の所感について述べます。

筆者が Checkov を知るきっかけは、同じように脆弱性スキャンツールである Trivy を使用してTerraformコードのmisconfiguration のスキャン方法について調べた際に、Checkov を発見し、試そうと思ったのがきっかけです。

Terraform とは

Terraform は、インフラストラクチャのコード化による自動化を実現するツールであり、オープンソースのコード管理ツールです。Terraform を使用することで、クラウドプロバイダーのAPIを使用して、クラウド上のリソースを管理できます。

Terraform は、クラウドネイティブなアプリケーションを構築するために、クラウドプロバイダーのAPIを使用してリソースを自動化することができます。
また、Terraform は、プロジェクト内で使用されるリソースの依存関係を理解して、リソースの作成順序を自動的に決定します。

Terraform の重要性は、クラウドインフラストラクチャ管理における自動化を実現し、リソースの管理にかかる時間やコストを削減できることにあります。
Terraform を使用することで、開発者や運用チームは、手動で行うことが困難な大規模なリソースの作成や変更を簡単に行うことができます。
これにより、アプリケーションの開発とデプロイに集中できるようになります。

Terraform は、AWS、Google Cloud、Microsoft Azure など、さまざまなクラウドプロバイダーに対応しています。
そのため、クラウドインフラストラクチャ管理に Terraform を使用することで、マルチクラウド環境を管理するための効率的な手段を提供することができます。

Iac を用いたインフラストラクチャのセキュリティに関する課題

クラウドインフラストラクチャのセキュリティには、いくつかの課題が存在します。
課題の一つは、リソースのミスコンフィグレーションにより情報セキュリティの脅威に晒されることです。
たとえば、パブリックなIPアドレスで VM インスタンスを起動する場合、必要な設定が行われていないために、インスタンスがファイアウォールなしで直接インターネットに接続されてしまう可能性があります。

さらに、APIキーの漏洩や、不適切なアクセス権限設定による不正利用、不適切なログ管理など、多くのセキュリティ課題が存在します。

これらのセキュリティ課題に対処するために、Trivy や Checkov を使って、Terraform コードの構文とリソースの設定を分析して、ミスコンフィギュレーションを検出することができます。

Trivy と Checkov とは

Trivy については以前私が他の記事で紹介したので、Google Cloud Build で Trivy を使用してコンテナイメージを脆弱性スキャンする を参照ください。
ここでは Checkov について説明します。

Checkov は、Bridgecrew 社が開発したオープンソースのポリシー・アズ・コードツールで、IaC テンプレート、コンテナイメージ、パイプライン構成などのインフラストラクチャー・コード(IaC)に対してセキュリティ問題をスキャンすることができます。
Bridgecrew 社は、クラウドセキュリティの自動化に特化したスタートアップ企業であり、AWS、Azure、Google Cloud などの主要なクラウドプロバイダーに対応しています。

また Checkov は、サポートしている IaC タイプも多く下記をサポートしています。

  • Terraform (for AWS, Google Cloud, Azure and OCI)
  • CloudFormation (including AWS SAM)
  • Azure Resource Manager (ARM)
  • Serverless framework
  • Helm charts
  • Kubernetes
  • Docker

継続的インテグレーション/継続的デプロイ (CI/CD) パイプラインに統合することもでき、開発およびデプロイ時にコードを自動的にスキャンできます。

詳しくは What is Checkov? をご参照ください。

また Trivy と Checkov の発音についてですが、Trivy については How to pronounce the name "Trivy"? より "トリヴィー" となりそうです。
Checkov については、私が調べたところ発音に関するページはなかったのですが、Bridgecrew 社が Youtube にて投稿している Checkov Reaches 2.0! にて、Checkov チームの方が "チェコフ" と発音しているように聞き取れました。

Trivy と Checkov を使った Terraform コードのスキャン方法

このセクションでは、実際に Trivy と Checkov を使って .tf コードをスキャンしてみます。

スキャン対象の terraform コード

今回スキャン対象のコードはTerraform を使用して基本的な Flask ウェブサーバーをデプロイするで紹介されている、こちらのコードを使います。

実際のコードは以下です。

https://github.com/terraform-google-modules/terraform-docs-samples/blob/135f46c0c432e7bbd367f8639f4e41ef02c20c4f/storage/flask_google_cloud_quickstart/main.tf

必要に応じてこちらのドキュメントにあるプロバイダ設定を追加して下さい。

terraform apply

上記のコマンドを実行すると、以下のリソースが作成されます。

terraform apply 実行結果
Terraform will perform the following actions:

  # google_compute_firewall.flask will be created
  + resource "google_compute_firewall" "flask" {
      + creation_timestamp = (known after apply)
      + destination_ranges = (known after apply)
      + direction          = (known after apply)
      + enable_logging     = (known after apply)
      + id                 = (known after apply)
      + name               = "flask-app-firewall"
      + network            = (known after apply)
      + priority           = 1000
      + project            = (known after apply)
      + self_link          = (known after apply)
      + source_ranges      = [
          + "0.0.0.0/0",
        ]

      + allow {
          + ports    = [
              + "5000",
            ]
          + protocol = "tcp"
        }
    }

  # google_compute_firewall.ssh will be created
  + resource "google_compute_firewall" "ssh" {
      + creation_timestamp = (known after apply)
      + destination_ranges = (known after apply)
      + direction          = "INGRESS"
      + enable_logging     = (known after apply)
      + id                 = (known after apply)
      + name               = "allow-ssh"
      + network            = (known after apply)
      + priority           = 1000
      + project            = (known after apply)
      + self_link          = (known after apply)
      + source_ranges      = [
          + "0.0.0.0/0",
        ]
      + target_tags        = [
          + "ssh",
        ]

      + allow {
          + ports    = [
              + "22",
            ]
          + protocol = "tcp"
        }
    }

  # google_compute_instance.default will be created
  + resource "google_compute_instance" "default" {
      + can_ip_forward          = false
      + cpu_platform            = (known after apply)
      + current_status          = (known after apply)
      + deletion_protection     = false
      + guest_accelerator       = (known after apply)
      + id                      = (known after apply)
      + instance_id             = (known after apply)
      + label_fingerprint       = (known after apply)
      + machine_type            = "f1-micro"
      + metadata_fingerprint    = (known after apply)
      + metadata_startup_script = "sudo apt-get update; sudo apt-get install -yq build-essential python3-pip rsync; pip install flask"
      + min_cpu_platform        = (known after apply)
      + name                    = "flask-vm"
      + project                 = (known after apply)
      + self_link               = (known after apply)
      + tags                    = [
          + "ssh",
        ]
      + tags_fingerprint        = (known after apply)
      + zone                    = "us-west1-a"

      + boot_disk {
          + auto_delete                = true
          + device_name                = (known after apply)
          + disk_encryption_key_sha256 = (known after apply)
          + kms_key_self_link          = (known after apply)
          + mode                       = "READ_WRITE"
          + source                     = (known after apply)

          + initialize_params {
              + image  = "debian-cloud/debian-11"
              + labels = (known after apply)
              + size   = (known after apply)
              + type   = (known after apply)
            }
        }

      + network_interface {
          + ipv6_access_type   = (known after apply)
          + name               = (known after apply)
          + network            = (known after apply)
          + network_ip         = (known after apply)
          + stack_type         = (known after apply)
          + subnetwork         = (known after apply)
          + subnetwork_project = (known after apply)

          + access_config {
              + nat_ip       = (known after apply)
              + network_tier = (known after apply)
            }
        }
    }

  # google_compute_network.vpc_network will be created
  + resource "google_compute_network" "vpc_network" {
      + auto_create_subnetworks                   = false
      + delete_default_routes_on_create           = false
      + gateway_ipv4                              = (known after apply)
      + id                                        = (known after apply)
      + internal_ipv6_range                       = (known after apply)
      + mtu                                       = 1460
      + name                                      = "my-custom-mode-network"
      + network_firewall_policy_enforcement_order = "AFTER_CLASSIC_FIREWALL"
      + project                                   = (known after apply)
      + routing_mode                              = (known after apply)
      + self_link                                 = (known after apply)
    }

  # google_compute_subnetwork.default will be created
  + resource "google_compute_subnetwork" "default" {
      + creation_timestamp         = (known after apply)
      + external_ipv6_prefix       = (known after apply)
      + fingerprint                = (known after apply)
      + gateway_address            = (known after apply)
      + id                         = (known after apply)
      + ip_cidr_range              = "10.0.1.0/24"
      + ipv6_cidr_range            = (known after apply)
      + name                       = "my-custom-subnet"
      + network                    = (known after apply)
      + private_ip_google_access   = (known after apply)
      + private_ipv6_google_access = (known after apply)
      + project                    = (known after apply)
      + purpose                    = (known after apply)
      + region                     = "us-west1"
      + secondary_ip_range         = (known after apply)
      + self_link                  = (known after apply)
      + stack_type                 = (known after apply)
    }

  # google_storage_bucket.default will be created
  + resource "google_storage_bucket" "default" {
      + force_destroy               = false
      + id                          = (known after apply)
      + location                    = "US"
      + name                        = (known after apply)
      + project                     = (known after apply)
      + public_access_prevention    = (known after apply)
      + self_link                   = (known after apply)
      + storage_class               = "STANDARD"
      + uniform_bucket_level_access = (known after apply)
      + url                         = (known after apply)

      + versioning {
          + enabled = true
        }
    }

  # random_id.bucket_prefix will be created
  + resource "random_id" "bucket_prefix" {
      + b64_std     = (known after apply)
      + b64_url     = (known after apply)
      + byte_length = 8
      + dec         = (known after apply)
      + hex         = (known after apply)
      + id          = (known after apply)
    }

Plan: 7 to add, 0 to change, 0 to destroy.

Trivy での terraform コードのスキャン

Tvivy によるコードスキャンを行うためには、 事前に Trivy をインストールする必要があります。
手順は Installing Trivy をご参照下さい。
本記事でインストール手順の解説は割愛します。

trivy config main.tf

上記のコマンドで terraform などの IaC ファイルをスキャンできます。

trivy config ./

また上記のコマンドのようにすることで、カレントディレクトリ内のすべての IaC ファイルをスキャンすることも出来ます。
例えば、Terraform、CloudFormation、Kubernetes、Helm Charts、Dockerfile の IaC ファイルを同じディレクトリに保持していたとしても、
Trivy は構成タイプを自動的に検出し、関連するポリシーを適用します。
詳しくは Misconfiguration Scanning をご参照ください。

trivy config main.tf の実行結果が以下です。
(スキャン結果に関する部分のみ抜粋しています)

main.tf (terraform)

Tests: 14 (SUCCESSES: 5, FAILURES: 9, EXCEPTIONS: 0)
Failures: 9 (UNKNOWN: 0, LOW: 3, MEDIUM: 4, HIGH: 1, CRITICAL: 1)

MEDIUM: Bucket has uniform bucket level access disabled.
════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════
When you enable uniform bucket-level access on a bucket, Access Control Lists (ACLs) are disabled, and only bucket-level Identity and Access Management (IAM) permissions grant access to that bucket and the objects it contains. You revoke all access granted by object ACLs and the ability to administrate permissions using bucket ACLs.

See https://avd.aquasec.com/misconfig/avd-gcp-0002
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 main.tf:79-87
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  79 ┌ resource "google_storage_bucket" "default" {
  80 │   name          = "${random_id.bucket_prefix.hex}-bucket-tfstate"
  81 │   force_destroy = false
  82 │   location      = "US"
  83 │   storage_class = "STANDARD"
  84 │   versioning {
  85 │     enabled = true
  86}
  87}
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────


CRITICAL: Firewall rule allows ingress traffic from multiple addresses on the public internet.
════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════
Network security rules should not use very broad subnets.

Where possible, segments should be broken into smaller subnets and avoid using the <code>/0</code> subnet.

See https://avd.aquasec.com/misconfig/avd-gcp-0027
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 main.tf:52
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  43   resource "google_compute_firewall" "ssh" {
  ..   
  52 [   source_ranges = ["0.0.0.0/0"]
  ..   
  54   }
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────


LOW: Subnetwork does not have VPC flow logs enabled.
════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════
VPC flow logs record information about all traffic, which is a vital tool in reviewing anomalous traffic.

See https://avd.aquasec.com/misconfig/avd-gcp-0029
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 main.tf:7-12
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   7 ┌ resource "google_compute_subnetwork" "default" {
   8 │   name          = "my-custom-subnet"
   9 │   ip_cidr_range = "10.0.1.0/24"
  10 │   region        = "us-west1"
  11 │   network       = google_compute_network.vpc_network.id
  12}
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────


MEDIUM: Instance allows use of project-level SSH keys.
════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════
Use of project-wide SSH keys means that a compromise of any one of these key pairs can result in all instances being compromised. It is recommended to use instance-level keys.

See https://avd.aquasec.com/misconfig/avd-gcp-0030
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 main.tf:17-39
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  17 ┌ resource "google_compute_instance" "default" {
  18 │   name         = "flask-vm"
  19 │   machine_type = "f1-micro"
  20 │   zone         = "us-west1-a"
  21 │   tags         = ["ssh"]
  2223 │   boot_disk {
  24 │     initialize_params {
  25 └       image = "debian-cloud/debian-11"
  ..   
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────


HIGH: Instance has a public IP allocated.
════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════
Instances should not be publicly exposed to the internet

See https://avd.aquasec.com/misconfig/avd-gcp-0031
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 main.tf:35-37
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  17   resource "google_compute_instance" "default" {
  ..   
  35 ┌     access_config {
  36# Include this section to give the VM an external IP address
  37}
  ..   
  39   }
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────


LOW: Instance disk encryption does not use a customer managed key.
════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════
Using unmanaged keys makes rotation and general management difficult.

See https://avd.aquasec.com/misconfig/avd-gcp-0033
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 main.tf:23-27
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  17   resource "google_compute_instance" "default" {
  ..   
  23 ┌   boot_disk {
  24 │     initialize_params {
  25 │       image = "debian-cloud/debian-11"
  26}
  27}
  ..   
  39   }
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────


MEDIUM: Instance does not have VTPM for shielded VMs enabled.
════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════
The virtual TPM provides numerous security measures to your VM.

See https://avd.aquasec.com/misconfig/avd-gcp-0041
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 main.tf:17-39
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  17 ┌ resource "google_compute_instance" "default" {
  18 │   name         = "flask-vm"
  19 │   machine_type = "f1-micro"
  20 │   zone         = "us-west1-a"
  21 │   tags         = ["ssh"]
  2223 │   boot_disk {
  24 │     initialize_params {
  25 └       image = "debian-cloud/debian-11"
  ..   
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────


MEDIUM: Instance does not have shielded VM integrity monitoring enabled.
════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════
Integrity monitoring helps you understand and make decisions about the state of your VM instances.

See https://avd.aquasec.com/misconfig/avd-gcp-0045
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 main.tf:17-39
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  17 ┌ resource "google_compute_instance" "default" {
  18 │   name         = "flask-vm"
  19 │   machine_type = "f1-micro"
  20 │   zone         = "us-west1-a"
  21 │   tags         = ["ssh"]
  2223 │   boot_disk {
  24 │     initialize_params {
  25 └       image = "debian-cloud/debian-11"
  ..   
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────


LOW: Storage bucket encryption does not use a customer-managed key.
════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════
Using unmanaged keys makes rotation and general management difficult.

See https://avd.aquasec.com/misconfig/avd-gcp-0066
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 main.tf:79-87
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  79 ┌ resource "google_storage_bucket" "default" {
  80 │   name          = "${random_id.bucket_prefix.hex}-bucket-tfstate"
  81 │   force_destroy = false
  82 │   location      = "US"
  83 │   storage_class = "STANDARD"
  84 │   versioning {
  85 │     enabled = true
  86}
  87}

該当ファイルや該当コードの行、 Severity 、問題の内容などを指摘しています。
See https://avd.aquasec.com/misconfig/xxx
を開くと各問題について、影響や直し方が記載されています。

Checkov での terraform コードのスキャン

Checkov によるコードスキャンを行うためには、 事前に Checkov をインストールする必要があります。
手順は Installing Checkov をご参照下さい。
本記事でインストール手順の解説は割愛します。

checkov --file ./main.tf

上記のコマンドで terraform などの IaC ファイルをスキャンできます。

checkov --directory ./

また上記のコマンドのようにすることで、カレントディレクトリ内のすべての IaC ファイルをスキャンすることも出来ます。
その他 CLI の例は Checkov Examples をご参照下さい。

checkov --file ./main.tf の実行結果が以下です。
(スキャン結果に関する部分のみ抜粋しています)

terraform scan results:

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

Check: CKV_GCP_31: "Ensure that instances are not configured to use the default service account with full access to all Cloud APIs"
        PASSED for resource: google_compute_instance.default
        File: /main.tf:17-39
        Guide: https://docs.bridgecrew.io/docs/bc_gcp_iam_2
Check: CKV_GCP_36: "Ensure that IP forwarding is not enabled on Instances"
        PASSED for resource: google_compute_instance.default
        File: /main.tf:17-39
        Guide: https://docs.bridgecrew.io/docs/bc_gcp_networking_12
Check: CKV_GCP_34: "Ensure that no instance in the project overrides the project setting for enabling OSLogin(OSLogin needs to be enabled in project metadata for all instances)"
        PASSED for resource: google_compute_instance.default
        File: /main.tf:17-39
        Guide: https://docs.bridgecrew.io/docs/bc_gcp_networking_10
Check: CKV_GCP_35: "Ensure 'Enable connecting to serial ports' is not enabled for VM Instance"
        PASSED for resource: google_compute_instance.default
        File: /main.tf:17-39
        Guide: https://docs.bridgecrew.io/docs/bc_gcp_networking_11
Check: CKV_GCP_88: "Ensure Google compute firewall ingress does not allow unrestricted mysql access"
        PASSED for resource: google_compute_firewall.ssh
        File: /main.tf:43-54
        Guide: https://docs.bridgecrew.io/docs/ensure-gcp-compute-firewall-ingress-does-not-allow-unrestricted-mysql-access
Check: CKV_GCP_75: "Ensure Google compute firewall ingress does not allow unrestricted FTP access"
        PASSED for resource: google_compute_firewall.ssh
        File: /main.tf:43-54
        Guide: https://docs.bridgecrew.io/docs/ensure-gcp-google-compute-firewall-ingress-does-not-allow-unrestricted-ftp-access
Check: CKV_GCP_77: "Ensure Google compute firewall ingress does not allow on ftp port"
        PASSED for resource: google_compute_firewall.ssh
        File: /main.tf:43-54
        Guide: https://docs.bridgecrew.io/docs/ensure-gcp-google-compute-firewall-ingress-does-not-allow-ftp-port-20-access
Check: CKV_GCP_3: "Ensure Google compute firewall ingress does not allow unrestricted rdp access"
        PASSED for resource: google_compute_firewall.ssh
        File: /main.tf:43-54
        Guide: https://docs.bridgecrew.io/docs/bc_gcp_networking_2
Check: CKV_GCP_106: "Ensure Google compute firewall ingress does not allow unrestricted http port 80 access"
        PASSED for resource: google_compute_firewall.ssh
        File: /main.tf:43-54
        Guide: https://docs.bridgecrew.io/docs/ensure-gcp-google-compute-firewall-ingress-does-not-allow-unrestricted-http-port-80-access
Check: CKV_GCP_88: "Ensure Google compute firewall ingress does not allow unrestricted mysql access"
        PASSED for resource: google_compute_firewall.flask
        File: /main.tf:59-68
        Guide: https://docs.bridgecrew.io/docs/ensure-gcp-compute-firewall-ingress-does-not-allow-unrestricted-mysql-access
Check: CKV_GCP_75: "Ensure Google compute firewall ingress does not allow unrestricted FTP access"
        PASSED for resource: google_compute_firewall.flask
        File: /main.tf:59-68
        Guide: https://docs.bridgecrew.io/docs/ensure-gcp-google-compute-firewall-ingress-does-not-allow-unrestricted-ftp-access
Check: CKV_GCP_77: "Ensure Google compute firewall ingress does not allow on ftp port"
        PASSED for resource: google_compute_firewall.flask
        File: /main.tf:59-68
        Guide: https://docs.bridgecrew.io/docs/ensure-gcp-google-compute-firewall-ingress-does-not-allow-ftp-port-20-access
Check: CKV_GCP_3: "Ensure Google compute firewall ingress does not allow unrestricted rdp access"
        PASSED for resource: google_compute_firewall.flask
        File: /main.tf:59-68
        Guide: https://docs.bridgecrew.io/docs/bc_gcp_networking_2
Check: CKV_GCP_2: "Ensure Google compute firewall ingress does not allow unrestricted ssh access"
        PASSED for resource: google_compute_firewall.flask
        File: /main.tf:59-68
        Guide: https://docs.bridgecrew.io/docs/bc_gcp_networking_1
Check: CKV_GCP_106: "Ensure Google compute firewall ingress does not allow unrestricted http port 80 access"
        PASSED for resource: google_compute_firewall.flask
        File: /main.tf:59-68
        Guide: https://docs.bridgecrew.io/docs/ensure-gcp-google-compute-firewall-ingress-does-not-allow-unrestricted-http-port-80-access
Check: CKV_GCP_78: "Ensure Cloud storage has versioning enabled"
        PASSED for resource: google_storage_bucket.default
        File: /main.tf:79-87
        Guide: https://docs.bridgecrew.io/docs/ensure-gcp-cloud-storage-has-versioning-enabled
Check: CKV_GCP_74: "Ensure that private_ip_google_access is enabled for Subnet"
        FAILED for resource: google_compute_subnetwork.default
        File: /main.tf:7-12
        Guide: https://docs.bridgecrew.io/docs/ensure-gcp-subnet-has-a-private-ip-google-access

                7  | resource "google_compute_subnetwork" "default" {
                8  |   name          = "my-custom-subnet"
                9  |   ip_cidr_range = "10.0.1.0/24"
                10 |   region        = "us-west1"
                11 |   network       = google_compute_network.vpc_network.id
                12 | }

Check: CKV_GCP_26: "Ensure that VPC Flow Logs is enabled for every subnet in a VPC Network"
        FAILED for resource: google_compute_subnetwork.default
        File: /main.tf:7-12
        Guide: https://docs.bridgecrew.io/docs/bc_gcp_logging_1

                7  | resource "google_compute_subnetwork" "default" {
                8  |   name          = "my-custom-subnet"
                9  |   ip_cidr_range = "10.0.1.0/24"
                10 |   region        = "us-west1"
                11 |   network       = google_compute_network.vpc_network.id
                12 | }

Check: CKV_GCP_76: "Ensure that Private google access is enabled for IPV6"
        FAILED for resource: google_compute_subnetwork.default
        File: /main.tf:7-12
        Guide: https://docs.bridgecrew.io/docs/ensure-gcp-private-google-access-is-enabled-for-ipv6

                7  | resource "google_compute_subnetwork" "default" {
                8  |   name          = "my-custom-subnet"
                9  |   ip_cidr_range = "10.0.1.0/24"
                10 |   region        = "us-west1"
                11 |   network       = google_compute_network.vpc_network.id
                12 | }

Check: CKV_GCP_32: "Ensure 'Block Project-wide SSH keys' is enabled for VM instances"
        FAILED for resource: google_compute_instance.default
        File: /main.tf:17-39
        Guide: https://docs.bridgecrew.io/docs/bc_gcp_networking_8

                17 | resource "google_compute_instance" "default" {
                18 |   name         = "flask-vm"
                19 |   machine_type = "f1-micro"
                20 |   zone         = "us-west1-a"
                21 |   tags         = ["ssh"]
                22 | 
                23 |   boot_disk {
                24 |     initialize_params {
                25 |       image = "debian-cloud/debian-11"
                26 |     }
                27 |   }
                28 | 
                29 |   # Install Flask
                30 |   metadata_startup_script = "sudo apt-get update; sudo apt-get install -yq build-essential python3-pip rsync; pip install flask"
                31 | 
                32 |   network_interface {
                33 |     subnetwork = google_compute_subnetwork.default.id
                34 | 
                35 |     access_config {
                36 |       # Include this section to give the VM an external IP address
                37 |     }
                38 |   }
                39 | }

Check: CKV_GCP_38: "Ensure VM disks for critical VMs are encrypted with Customer Supplied Encryption Keys (CSEK)"
        FAILED for resource: google_compute_instance.default
        File: /main.tf:17-39
        Guide: https://docs.bridgecrew.io/docs/encrypt-boot-disks-for-instances-with-cseks

                17 | resource "google_compute_instance" "default" {
                18 |   name         = "flask-vm"
                19 |   machine_type = "f1-micro"
                20 |   zone         = "us-west1-a"
                21 |   tags         = ["ssh"]
                22 | 
                23 |   boot_disk {
                24 |     initialize_params {
                25 |       image = "debian-cloud/debian-11"
                26 |     }
                27 |   }
                28 | 
                29 |   # Install Flask
                30 |   metadata_startup_script = "sudo apt-get update; sudo apt-get install -yq build-essential python3-pip rsync; pip install flask"
                31 | 
                32 |   network_interface {
                33 |     subnetwork = google_compute_subnetwork.default.id
                34 | 
                35 |     access_config {
                36 |       # Include this section to give the VM an external IP address
                37 |     }
                38 |   }
                39 | }

Check: CKV_GCP_30: "Ensure that instances are not configured to use the default service account"
        FAILED for resource: google_compute_instance.default
        File: /main.tf:17-39
        Guide: https://docs.bridgecrew.io/docs/bc_gcp_iam_1

                17 | resource "google_compute_instance" "default" {
                18 |   name         = "flask-vm"
                19 |   machine_type = "f1-micro"
                20 |   zone         = "us-west1-a"
                21 |   tags         = ["ssh"]
                22 | 
                23 |   boot_disk {
                24 |     initialize_params {
                25 |       image = "debian-cloud/debian-11"
                26 |     }
                27 |   }
                28 | 
                29 |   # Install Flask
                30 |   metadata_startup_script = "sudo apt-get update; sudo apt-get install -yq build-essential python3-pip rsync; pip install flask"
                31 | 
                32 |   network_interface {
                33 |     subnetwork = google_compute_subnetwork.default.id
                34 | 
                35 |     access_config {
                36 |       # Include this section to give the VM an external IP address
                37 |     }
                38 |   }
                39 | }

Check: CKV_GCP_39: "Ensure Compute instances are launched with Shielded VM enabled"
        FAILED for resource: google_compute_instance.default
        File: /main.tf:17-39
        Guide: https://docs.bridgecrew.io/docs/bc_gcp_general_y

                17 | resource "google_compute_instance" "default" {
                18 |   name         = "flask-vm"
                19 |   machine_type = "f1-micro"
                20 |   zone         = "us-west1-a"
                21 |   tags         = ["ssh"]
                22 | 
                23 |   boot_disk {
                24 |     initialize_params {
                25 |       image = "debian-cloud/debian-11"
                26 |     }
                27 |   }
                28 | 
                29 |   # Install Flask
                30 |   metadata_startup_script = "sudo apt-get update; sudo apt-get install -yq build-essential python3-pip rsync; pip install flask"
                31 | 
                32 |   network_interface {
                33 |     subnetwork = google_compute_subnetwork.default.id
                34 | 
                35 |     access_config {
                36 |       # Include this section to give the VM an external IP address
                37 |     }
                38 |   }
                39 | }

Check: CKV_GCP_40: "Ensure that Compute instances do not have public IP addresses"
        FAILED for resource: google_compute_instance.default
        File: /main.tf:17-39
        Guide: https://docs.bridgecrew.io/docs/bc_gcp_public_2

                17 | resource "google_compute_instance" "default" {
                18 |   name         = "flask-vm"
                19 |   machine_type = "f1-micro"
                20 |   zone         = "us-west1-a"
                21 |   tags         = ["ssh"]
                22 | 
                23 |   boot_disk {
                24 |     initialize_params {
                25 |       image = "debian-cloud/debian-11"
                26 |     }
                27 |   }
                28 | 
                29 |   # Install Flask
                30 |   metadata_startup_script = "sudo apt-get update; sudo apt-get install -yq build-essential python3-pip rsync; pip install flask"
                31 | 
                32 |   network_interface {
                33 |     subnetwork = google_compute_subnetwork.default.id
                34 | 
                35 |     access_config {
                36 |       # Include this section to give the VM an external IP address
                37 |     }
                38 |   }
                39 | }

Check: CKV_GCP_2: "Ensure Google compute firewall ingress does not allow unrestricted ssh access"
        FAILED for resource: google_compute_firewall.ssh
        File: /main.tf:43-54
        Guide: https://docs.bridgecrew.io/docs/bc_gcp_networking_1

                43 | resource "google_compute_firewall" "ssh" {
                44 |   name = "allow-ssh"
                45 |   allow {
                46 |     ports    = ["22"]
                47 |     protocol = "tcp"
                48 |   }
                49 |   direction     = "INGRESS"
                50 |   network       = google_compute_network.vpc_network.id
                51 |   priority      = 1000
                52 |   source_ranges = ["0.0.0.0/0"]
                53 |   target_tags   = ["ssh"]
                54 | }

Check: CKV_GCP_114: "Ensure public access prevention is enforced on Cloud Storage bucket"
        FAILED for resource: google_storage_bucket.default
        File: /main.tf:79-87

                79 | resource "google_storage_bucket" "default" {
                80 |   name          = "${random_id.bucket_prefix.hex}-bucket-tfstate"
                81 |   force_destroy = false
                82 |   location      = "US"
                83 |   storage_class = "STANDARD"
                84 |   versioning {
                85 |     enabled = true
                86 |   }
                87 | }

Check: CKV_GCP_62: "Bucket should log access"
        FAILED for resource: google_storage_bucket.default
        File: /main.tf:79-87
        Guide: https://docs.bridgecrew.io/docs/bc_gcp_logging_2

                79 | resource "google_storage_bucket" "default" {
                80 |   name          = "${random_id.bucket_prefix.hex}-bucket-tfstate"
                81 |   force_destroy = false
                82 |   location      = "US"
                83 |   storage_class = "STANDARD"
                84 |   versioning {
                85 |     enabled = true
                86 |   }
                87 | }

Check: CKV_GCP_29: "Ensure that Cloud Storage buckets have uniform bucket-level access enabled"
        FAILED for resource: google_storage_bucket.default
        File: /main.tf:79-87
        Guide: https://docs.bridgecrew.io/docs/bc_gcp_gcs_2

                79 | resource "google_storage_bucket" "default" {
                80 |   name          = "${random_id.bucket_prefix.hex}-bucket-tfstate"
                81 |   force_destroy = false
                82 |   location      = "US"
                83 |   storage_class = "STANDARD"
                84 |   versioning {
                85 |     enabled = true
                86 |   }
                87 | }

該当ファイルや該当コードの行、問題の内容などを指摘しています。
Guide https://docs.bridgecrew.io/docs/xxx
を開くと各問題について、影響や直し方が記載されています。

Trivy では Iac ファイルをスキャンすると、各脆弱性の Severity が表示されますが、
スキャン結果からも分かる通り Checkov では Severity が表示されません。
Checkov でスキャン結果へ Severity を表示させたり、Severity でソートするには、
Bridgecrew API token (brigecrew へ free account でサインアップすることで取得可能) が必要みたいです。
詳しくは、Prioritize, skip, and fail with policy severities in Checkov をご参照下さい。

スキャン結果の比較

Trivy と Checkov のスキャン結果を比較すると、Trivy でのテスト項目が 14 個 (そのうち Successes が 5 、Failures が 9 )に対して、Checkov ではテスト項目が 28 個 (そのうち Passed が 16 つ、Failed が 12)と、今回対象にしたコードでは Checkov が Trivy の 2 倍の項目をスキャンしている項目が有ることが分かりました。
スキャン結果の内容を筆者が確認する限り、Trivy で検知されている項目は全て Checkov でも含まれているように見受けられました。
また、Trivy は、

Trivy is a comprehensive and versatile security scanner.

と謳っているの対し、
Checkov では、

Checkov is a static code analysis tool for infrastructure as code (IaC) and also a software composition analysis (SCA) tool for images and open source packages.

と謳っているので、IaCファイルのスキャンにおいては、Checkovの方が包括的な印象を抱きました。
しかし、Checkov では API key を取得しない限り、各項目に対して Severity が出力されないといった側面もあるので、とりあえずスキャン結果の各項目の Severity を確認したい場合は、 Trivy の方がとっつきやすいと思いました。

所感

今でこそ Trivy は コンテナ・Kubernetes・コードリポジトリ・クラウドなどの脆弱性・設定ミス・Secret を発見する 包括的なセキュリティスキャナーとして使われていますが、
OSS活動を通して掴んだ海外キャリア。英語力よりも技術力を大切にチャンスを掴むの記事より、元はコンテナ脆弱性ツールであったことが分かります。
そのため Trivy と Checkov では、得意な領域が違うと私は思っております。
端的に言うと、Trivy はコンテナで使用されている古いパッケージに関連する脆弱性の検出が得意で、Checkov は IaC ファイル内の構成ミスやセキュリティ問題の検出が得意だと思っております。
全体として、Trivy と Checkov は両方ともコードとしてのインフラストラクチャの脆弱性を検出が出来る便利なツールですが、焦点が異なり、異なる種類の脆弱性を検出する可能性があることに注意することが重要だと思います。
より包括的な脆弱性評価を行うために、両方のツールを一緒に使用することも考えられますが、
コンテナの脆弱性検出には Trivy 、IaC ファイル内の構成ミスやセキュリティ問題の検出には Checkov を用いるといように、使い分けをしたいと私は思いました。

Discussion