GitHub Actions で Terraform の plan 結果を Conftest(OPA)でポリシーチェックしてみた

GitHub Actions で Terraform の plan 結果を Conftest(OPA)でポリシーチェックしてみた

2026.07.02

GitHub Actions で terraform plan の JSON を Conftest(OPA)でポリシーチェックし、違反があれば CI を落とす仕組みを作ってみました。

「本番のセキュリティグループを 0.0.0.0/0 に開けない」「必須タグが無いリソースは作らせない」のような組織の決まりを、機械的に強制する仕組みです。

以前OpenTaco + Conftest を使ってやってみましたが、今回はOpenTaco無しConftest単体でやってみます。

https://dev.classmethod.jp/articles/opentaco-opa-policy-test-inline/

前提

このワークフローは aws-actions/configure-aws-credentials で OIDC 認証し、GitHub Actions から IAM ロールを AssumeRole する構成です。

実行する前に、AWS側でOIDCのIAMロールを準備し、ロールのARNをリポジトリのSecret AWS_ROLE_TO_ASSUME に登録しておく必要があります。

IAMロールの作成手順は以下の記事にまとめています。

https://dev.classmethod.jp/articles/github-actions-oidc-environments-multi-account-deploy/

この記事は環境ごとにロールを分ける構成ですが、OIDCプロバイダーの参照とIAMロールの信頼ポリシー作成の部分は、今回のような単一ロール構成でもそのまま使えます。

ポリシー(Rego)作成

今回はルールを3つ用意しました。

  • セキュリティグループの ingress に 0.0.0.0/0 があれば deny
  • 必須タグ(Environment / Owner)が無い AWS リソースを deny
  • 暗号化されていない EBS ボリュームを deny

各ルールは以下です。

policy/network.rego
package main

deny contains msg if {
	some change in input.resource_changes
	change.type == "aws_security_group"
	some action in change.change.actions
	action in {"create", "update"}
	some ingress in change.change.after.ingress
	some cidr in ingress.cidr_blocks
	cidr == "0.0.0.0/0"
	msg := sprintf("%s: ingress open to 0.0.0.0/0 (port %d)", [change.address, ingress.from_port])
}
policy/tags.rego
package main

required_tags := {"Environment", "Owner"}

taggable_types := {"aws_vpc", "aws_security_group", "aws_ebs_volume", "aws_instance"}

deny contains msg if {
	some change in input.resource_changes
	taggable_types[change.type]
	some action in change.change.actions
	action in {"create", "update"}
	tags := object.get(change.change.after, "tags", {})
	missing := required_tags - {k | some k, _ in tags}
	count(missing) > 0
	msg := sprintf("%s %s: required tags missing: %v", [change.type, change.address, missing])
}
policy/storage.rego
package main

deny contains msg if {
	some change in input.resource_changes
	change.type == "aws_ebs_volume"
	some action in change.change.actions
	action in {"create", "update"}
	not change.change.after.encrypted
	msg := sprintf("%s: EBS volume not encrypted", [change.address])
}

3つとも input.resource_changes[]change.after を見て、actionscreate または update が含まれる変更を対象にしています。新規作成や置き換えだけでなく、既存の SG に 0.0.0.0/0 の ingress を追記するような in-place update も検知対象です。

GitHub Actions ワークフロー作成

実際に動かしたワークフローです。

.github/workflows/terraform-opa-check.yml
name: terraform-opa-check
on:
  pull_request:
    types: [opened, synchronize]
    paths:
      - "terraform-conftest-opa/**"
permissions:
  contents: read
  pull-requests: write
  id-token: write
jobs:
  opa-check:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: terraform-conftest-opa
    steps:
      - uses: actions/checkout@v7
      - uses: aws-actions/configure-aws-credentials@v6
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
          aws-region: ap-northeast-1
      - uses: hashicorp/setup-terraform@v4
      - run: terraform init
      - run: terraform plan -out=tfplan
      - run: terraform show -json tfplan > plan.json
      - shell: bash
        run: |
          docker run --rm -v "$PWD":/project \
            openpolicyagent/conftest:v0.68.2 \
            test plan.json --policy policy --output json | tee conftest-result.json
      - if: always()
        uses: actions/upload-artifact@v7
        with:
          name: conftest-result
          path: terraform-conftest-opa/conftest-result.json

working-directory: terraform-conftest-opa にしているので、$PWD はこのサブディレクトリを指します。

Conftest 専用の公式 GitHub Actions アクションはありません。OPA org が出している setup-opaopa バイナリ向けで、conftest は別バイナリのため入りません。

そこで OPA org 公式の Docker イメージ openpolicyagent/conftestdocker run で実行することにしました。

conftest test の結果は --output jsonconftest-result.json に残し、actions/upload-artifact でアーティファクト化しています。

| tee conftest-result.json の部分に注意です。

デフォルトでは、bash -e {0}で実行され pipefail が無効になります。

パイプの終了コードは常に exit 0 で終わる tee のものになり、Conftest が違反で失敗してもジョブに伝わりません

shell: bash を明示すると pipefail が自動で有効になるため、上記の対策として設定しました。

GitHub Docs: defaults.run.shell

動作確認

違反ありと違反なしのTerraformコードを用意して動作を確認しました。差分は main.tf の以下の部分です。

main.tf
 resource "aws_vpc" "main" {
   cidr_block           = var.vpc_cidr
   enable_dns_hostnames = true

   tags = {
-    Name = "${var.instance_name}-vpc"
+    Name        = "${var.instance_name}-vpc"
+    Environment = "dev"
+    Owner       = "sato.masaki"
   }
 }

 resource "aws_security_group" "web" {
   name_prefix = "${var.instance_name}-sg-"
   description = "Security group for ${var.instance_name}"
   vpc_id      = aws_vpc.main.id

   ingress {
-    description = "HTTP"
+    description = "HTTP from office"
     from_port   = 80
     to_port     = 80
     protocol    = "tcp"
-    cidr_blocks = ["0.0.0.0/0"]
+    cidr_blocks = ["203.0.113.0/24"]
   }

   egress {
     from_port   = 0
     to_port     = 0
     protocol    = "-1"
     cidr_blocks = ["0.0.0.0/0"]
   }

   tags = {
-    Name = "${var.instance_name}-sg"
+    Name        = "${var.instance_name}-sg"
+    Environment = "dev"
+    Owner       = "sato.masaki"
   }
 }

 resource "aws_ebs_volume" "data" {
   availability_zone = "ap-northeast-1a"
   size              = 10
+  encrypted         = true

   tags = {
-    Name = "${var.instance_name}-vol"
+    Name        = "${var.instance_name}-vol"
+    Environment = "dev"
+    Owner       = "sato.masaki"
   }
 }
内容 結果
違反あり plan(- 側) failure(意図通り)
必須タグ・SG ingress・EBS暗号化をすべて修正した plan(+ 側) success(意図通り)

違反あり plan(- 側)では、conftest-result.json に5件の failures が出ます。

conftest-result.json(違反あり。5件中2件を抜粋)
[
	{
		"filename": "plan.json",
		"namespace": "main",
		"successes": 0,
		"failures": [
			{
				"msg": "aws_security_group.web: ingress open to 0.0.0.0/0 (port 80)",
				"metadata": { "query": "data.main.deny" }
			},
			{
				"msg": "aws_ebs_volume.data: EBS volume not encrypted",
				"metadata": { "query": "data.main.deny" }
			}
		]
	}
]

修正後の plan(+ 側)では、conftest-result.json の中身もシンプルになりました。

conftest-result.json(違反修正後)
[
	{
		"filename": "plan.json",
		"namespace": "main",
		"successes": 3
	}
]

おわりに

GitHub Actions で terraform plan の JSON を Conftest 単体でポリシーチェックする仕組みを試してみました。

OPA org 公式の Docker イメージを使うだけで動くので、専用アクションが無くても導入のハードルは低かったです。

この記事をシェアする

関連記事