Checkov で Terraform の脆弱性を検知する: insecure vs secure の比較デモ

重岡 正 ·  Fri, April 10, 2026

Terraform の terraform apply を実行した後に「この S3 バケット、暗号化されてなかった」と気づくのは避けたい状況です。IaC のセキュリティは、コードを書いた時点で検証できるのが理想です。

CheckovPrisma Cloud(旧 Bridgecrew)が開発するオープンソースの静的解析ツールです。Terraform、CloudFormation、Kubernetes、Dockerfile など多くのフレームワークに対応し、セキュリティとコンプライアンスの問題を planapply なしで検知できます。

本記事では、意図的に脆弱な Terraform コードと修正済みコードを並べたデモリポジトリを作成し、Checkov がどのように脆弱性を検知するかを実際に確認します。

デモリポジトリ: codenote-net/checkov-terraform-demo

デモリポジトリの構成

リポジトリは insecure/secure/ の 2 つのディレクトリで構成されています。

checkov-terraform-demo/
├── insecure/          # 意図的に脆弱な Terraform コード
│   ├── provider.tf
│   ├── s3.tf
│   ├── sg.tf
│   ├── rds.tf
│   ├── iam.tf
│   └── cloudtrail.tf
├── secure/            # 修正済みの Terraform コード
│   ├── provider.tf
│   ├── s3.tf
│   ├── sg.tf
│   ├── rds.tf
│   ├── iam.tf
│   └── cloudtrail.tf
└── .github/workflows/
    └── checkov.yml

対象リソースは AWS の 5 種類です。

  • S3: バケットの暗号化、パブリックアクセス、バージョニング
  • Security Group: ポート開放範囲、SSH アクセス制限
  • RDS: ストレージ暗号化、パブリックアクセス、Multi-AZ
  • IAM: ポリシーの権限範囲
  • CloudTrail: ログの暗号化、バリデーション

Checkov は静的解析ツールなので、terraform initterraform plan も不要です。.tf ファイルさえあれば、即座にスキャンできます。

脆弱なコードを書く

まず、insecure/ ディレクトリに意図的に脆弱なコードを配置します。実装の詳細は PR #1 で確認できます。

S3: 暗号化なし、パブリックアクセス許可

resource "aws_s3_bucket" "data" {
  bucket = "demo-insecure-bucket"
}
 
resource "aws_s3_bucket_acl" "data" {
  bucket = aws_s3_bucket.data.id
  acl    = "public-read"
}

暗号化の設定がなく、ACL が public-read になっています。バージョニング、アクセスログ、パブリックアクセスブロックもありません。

Security Group: 全ポートを全 IP に開放

resource "aws_security_group" "web" {
  name        = "demo-insecure-sg"
  description = "Insecure security group for demo"
 
  ingress {
    from_port   = 0
    to_port     = 65535
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
 
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
 
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

TCP の全ポート(0-65535)を 0.0.0.0/0 に開放し、SSH も全 IP からアクセスできる状態です。

RDS: 暗号化なし、パブリックアクセス有効

resource "aws_db_instance" "main" {
  identifier        = "demo-insecure-db"
  engine            = "mysql"
  engine_version    = "8.0"
  instance_class    = "db.t3.micro"
  allocated_storage = 20
  username          = "admin"
  password          = "password123"
 
  publicly_accessible = true
  storage_encrypted   = false
  skip_final_snapshot = true
}

ストレージが暗号化されておらず、パブリックアクセスが有効です。パスワードもハードコードされています。

IAM: ワイルドカードポリシー

resource "aws_iam_policy" "admin" {
  name = "demo-insecure-policy"
 
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect   = "Allow"
        Action   = "*"
        Resource = "*"
      }
    ]
  })
}

ActionResource の両方が * になっており、全リソースに対する全操作を許可しています。

CloudTrail: 暗号化なし、バリデーション無効

resource "aws_cloudtrail" "main" {
  name                       = "demo-insecure-trail"
  s3_bucket_name             = aws_s3_bucket.data.id
  enable_log_file_validation = false
}

KMS 暗号化がなく、ログファイルのバリデーションも無効です。

Checkov でスキャンする

Checkov のインストールは pip で行います。

pip install checkov

insecure/ ディレクトリに対してスキャンを実行します。

checkov -d insecure/ --framework terraform

結果は Passed: 13 / Failed: 37 でした。

検知された 37 個の脆弱性

リソース別に、検知されたチェック ID と内容をまとめます。

S3(aws_s3_bucket.data): 8 件

チェック ID内容
CKV_AWS_18アクセスログが無効
CKV_AWS_20パブリック READ アクセスを許可
CKV_AWS_21バージョニングが無効
CKV_AWS_144クロスリージョンレプリケーションが未設定
CKV_AWS_145KMS による暗号化が未設定
CKV2_AWS_6パブリックアクセスブロックが未設定
CKV2_AWS_61ライフサイクル設定が未設定
CKV2_AWS_62イベント通知が無効

Security Group(aws_security_group.web): 6 件

チェック ID内容
CKV_AWS_23ルールに description がない
CKV_AWS_24SSH(22)が 0.0.0.0/0 に開放
CKV_AWS_25RDP(3389)が 0.0.0.0/0 に開放
CKV_AWS_260HTTP(80)が 0.0.0.0/0 に開放
CKV_AWS_382全ポートで Egress が 0.0.0.0/0 に開放
CKV2_AWS_5リソースにアタッチされていない

RDS(aws_db_instance.main): 9 件

チェック ID内容
CKV_AWS_16ストレージ暗号化が無効
CKV_AWS_17パブリックアクセスが有効
CKV_AWS_118Enhanced Monitoring が無効
CKV_AWS_129ログ出力が無効
CKV_AWS_157Multi-AZ が無効
CKV_AWS_161IAM 認証が無効
CKV_AWS_226マイナーバージョン自動アップグレードが無効
CKV_AWS_293削除保護が無効
CKV2_AWS_60スナップショットへのタグコピーが無効

IAM(aws_iam_policy.admin): 9 件

チェック ID内容
CKV_AWS_62完全な管理者権限を付与
CKV_AWS_63Action に * を許可
CKV_AWS_286権限昇格が可能
CKV_AWS_287認証情報の露出が可能
CKV_AWS_288データの持ち出しが可能
CKV_AWS_289リソースが無制限に公開される
CKV_AWS_290無制限な書き込みアクセス
CKV_AWS_355制限可能なアクションに * リソースを許可
CKV2_AWS_40IAM の完全な権限を付与

CloudTrail(aws_cloudtrail.main): 5 件

チェック ID内容
CKV_AWS_35KMS による暗号化が未設定
CKV_AWS_36ログファイルのバリデーションが無効
CKV_AWS_67全リージョンで有効化されていない
CKV_AWS_252SNS トピックが未設定
CKV2_AWS_10CloudWatch Logs と統合されていない

1 つの IAM ポリシーに Action: "*" を書いただけで 9 件の FAILED が出ます。Checkov は単純なパターンマッチではなく、権限昇格やデータ持ち出しの可能性まで分析しています。

修正済みコードで PASSED を目指す

secure/ ディレクトリに修正済みコードを配置します。実装の詳細は PR #2 で確認できます。

修正のポイントをリソース別に紹介します。

S3: 暗号化、アクセス制御、ライフサイクル管理

resource "aws_s3_bucket" "data" {
  bucket = "demo-secure-bucket"
}
 
resource "aws_s3_bucket_versioning" "data" {
  bucket = aws_s3_bucket.data.id
 
  versioning_configuration {
    status = "Enabled"
  }
}
 
resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
  bucket = aws_s3_bucket.data.id
 
  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "aws:kms"
    }
    bucket_key_enabled = true
  }
}
 
resource "aws_s3_bucket_public_access_block" "data" {
  bucket = aws_s3_bucket.data.id
 
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

KMS 暗号化、バージョニング、パブリックアクセスブロックを有効化しています。これに加えて、アクセスログ、ライフサイクル設定、クロスリージョンレプリケーションも設定しています(全コードは secure/s3.tf を参照)。

Security Group: ポートとアクセス元の制限

variable "allowed_ssh_cidr" {
  description = "CIDR block allowed to SSH"
  type        = string
  default     = "203.0.113.0/24"
}
 
resource "aws_security_group" "web" {
  name        = "demo-secure-sg"
  description = "Secure security group for demo - restricted access"
 
  ingress {
    description = "HTTPS from anywhere"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
 
  ingress {
    description = "SSH from allowed CIDR only"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = [var.allowed_ssh_cidr]
  }
 
  egress {
    description = "Allow HTTPS outbound"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

全ポート開放を廃止し、HTTPS(443)のみ許可しています。SSH は変数で指定した特定の CIDR ブロックに制限し、各ルールに description を追加しています。CKV2_AWS_5(リソースにアタッチされていない)を解消するため、EC2 インスタンスも定義しています。

RDS: 暗号化と可用性の強化

variable "db_password" {
  description = "Database master password"
  type        = string
  sensitive   = true
}
 
resource "aws_db_instance" "main" {
  identifier        = "demo-secure-db"
  engine            = "mysql"
  engine_version    = "8.0"
  instance_class    = "db.t3.micro"
  allocated_storage = 20
  username          = "admin"
  password          = var.db_password
 
  publicly_accessible                 = false
  storage_encrypted                   = true
  copy_tags_to_snapshot               = true
  auto_minor_version_upgrade          = true
  deletion_protection                 = true
  iam_database_authentication_enabled = true
  multi_az                            = true
  monitoring_interval                 = 60
  monitoring_role_arn                 = aws_iam_role.rds_monitoring.arn
  enabled_cloudwatch_logs_exports     = ["audit", "error", "general", "slowquery"]
}

パスワードを sensitive 変数に切り出し、ストレージ暗号化、Multi-AZ、IAM 認証、Enhanced Monitoring、CloudWatch Logs へのログ出力を有効化しています。

IAM: 最小権限の原則

resource "aws_iam_policy" "app" {
  name        = "demo-secure-policy"
  description = "Least privilege policy for application"
 
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "s3:GetObject",
          "s3:PutObject",
          "s3:ListBucket"
        ]
        Resource = [
          "arn:aws:s3:::demo-secure-bucket",
          "arn:aws:s3:::demo-secure-bucket/*"
        ]
      }
    ]
  })
}

* を廃止し、必要な S3 操作だけを特定のバケットに対して許可しています。

CloudTrail: 暗号化と監視の統合

resource "aws_cloudtrail" "main" {
  name                       = "demo-secure-trail"
  s3_bucket_name             = aws_s3_bucket.data.id
  is_multi_region_trail      = true
  enable_log_file_validation = true
  kms_key_id                 = aws_kms_key.cloudtrail.arn
  cloud_watch_logs_group_arn = "${aws_cloudwatch_log_group.cloudtrail.arn}:*"
  cloud_watch_logs_role_arn  = aws_iam_role.cloudtrail_cloudwatch.arn
 
  sns_topic_name = aws_sns_topic.cloudtrail.arn
}

KMS 暗号化、ログバリデーション、全リージョン対応、CloudWatch Logs 統合、SNS 通知を設定しています。

修正後のスキャン結果

checkov -d secure/ --framework terraform

結果は Passed: 111 / Failed: 0 でした。37 個の FAILED がすべて解消され、チェック項目自体も 50 件から 111 件に増えています。修正にあたってリソース(ログバケット、KMS キー、IAM ロールなど)を追加したため、チェック対象が増えたことが要因です。

GitHub Actions で CI に組み込む

PR ごとに Checkov を自動実行し、結果を PR コメントに投稿する GitHub Actions ワークフローを設定しています。

name: Checkov
 
on:
  push:
    branches: [main]
    paths:
      - "insecure/**"
      - "secure/**"
      - ".github/workflows/checkov.yml"
  pull_request:
    branches: [main]
    paths:
      - "insecure/**"
      - "secure/**"
      - ".github/workflows/checkov.yml"
 
permissions:
  contents: read
  pull-requests: write
 
jobs:
  scan-insecure:
    name: "Scan insecure/"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - name: Run Checkov on insecure/
        id: checkov
        uses: bridgecrewio/checkov-action@v12
        with:
          directory: insecure/
          framework: terraform
          soft_fail: true
          output_format: cli
 
  scan-secure:
    name: "Scan secure/"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - name: Check if secure/ exists
        id: check
        run: |
          if [ -d "secure" ] && [ "$(ls -A secure/)" ]; then
            echo "exists=true" >> "$GITHUB_OUTPUT"
          else
            echo "exists=false" >> "$GITHUB_OUTPUT"
          fi
 
      - name: Run Checkov on secure/
        if: steps.check.outputs.exists == 'true'
        uses: bridgecrewio/checkov-action@v12
        with:
          directory: secure/
          framework: terraform
          soft_fail: false
          output_format: cli

ポイントは 2 つあります。

  • insecure/soft_fail: true で、FAILED があっても CI を通す(脆弱なコードは意図的なため)
  • secure/soft_fail: false で、FAILED があれば CI を落とす

paths フィルターにより、README の更新など .tf ファイルに関係ない変更ではスキャンをスキップします。

まとめ

Checkov を使うことで、Terraform コードの脆弱性を apply の前に検知できます。

今回のデモでは、5 つの AWS リソースに対して 37 個の脆弱性を検知し、修正後に 111 個のチェックをすべて PASSED にしました。Checkov は terraform init すら不要で、.tf ファイルに対して即座にスキャンを実行できるため、開発フローへの導入コストが低いのが魅力です。

GitHub Actions に組み込めば、PR のたびに自動でセキュリティチェックが走ります。「脆弱な設定をうっかりマージしてしまった」というリスクを、コードレビューだけに頼らず仕組みで防げるようになります。

デモリポジトリのコードはすべて公開しています。手元で checkov -d insecure/ を実行して、検知結果を確認してみてください。

以上、Terraform の脆弱性は apply する前に潰していきたい、現場からお送りしました。