CloudFormation Hooks+Guardで月50件の設定ミスを自動検出。二階建て運用の現実的な実装記

セキュリティグループ全開放、暗号化忘れ…。手作業でのコンプライアンスチェックはもう限界。HooksとGuardを組み合わせた実装から失敗を含めた6ヶ月の運用知見まで。

CloudFormation Hooks・Guardを導入した背景

うちのチームで月20〜30件のCloudFormationスタック作成・更新をしてるんですが、去年の秋くらいから「あ、これセキュリティグループが全開放になってる」「RDSの暗号化忘れた」みたいな設定ミスがレビュー段階で引っかかるようになってました。最初は手作業でチェックリストを作ってたんですけど、正直マンパワーで対応するのは限界がある。そこで2026年2月くらいにCloudFormation HooksとGuardの本格導入を決めたんです。

今回はその6ヶ月の導入記を、失敗も含めて書きます。

CloudFormation HooksとGuardの違い

まず混乱しやすいんですが、HooksとGuardは別物です。

CloudFormation Hooksは、スタック作成・更新時にテンプレート検証をリアルタイムで実行するマネージドサービス。2024年から一般公開されて、2026年にはかなり安定してます。組織ポリシーベースで強制できるから、デプロイ段階での検証が確実。ただし公開ルールセットに限定されるのと、カスタムロジックは難しい。

CloudFormation Guardは、独立したオープンソースツール。Guard Rules Language(Ruby DSL)で自分たちのルールを書ける。ローカルで実行することもできるし、CI/CDパイプラインに組み込むのが簡単。ただし実行環境を自分で用意する必要があります。

正直なところ、どっちを選ぶか悩みました。うちはControl Towerで基盤構築してるので、最初はHooksだけで統制しようと思ってたんです。でも検証してみたら、Hooksの公開ルールセットだけだと業務要件を100%満たせない。結局、Hooksで基本的なセキュリティ・コンプライアンス检验をして、Guardでカスタムルールを書く—という二階建て運用に落ち着きました。

実装構成図

graph TB
    subgraph Organization["AWS Organization"]
        subgraph RootOU["Root OU"]
            HooksPolicy["CloudFormation Hooks Policy"]
        end
        subgraph WorkloadOU["Workload OU"]
            DevAcct["Dev Account"]
            ProdAcct["Prod Account"]
        end
    end

    subgraph DevPipeline["Dev Account CI/CD"]
        GitRepo["GitHub"]
        CodePipeline["CodePipeline"]
        GuardLint["cfn-lint + Guard"]
        CodeBuild["CodeBuild"]
    end

    subgraph CFExecution["CloudFormation Execution"]
        Templates["CFn Templates"]
        HooksValidation["CloudFormation Hooks<br/>Built-in Rules"]
        CustomHooks["Custom Hooks<br/>Lambda"]
        Deployment["Deploy/Update"]
    end

    subgraph Monitoring["Monitoring"]
        SNS["SNS Notification"]
        CloudWatch["CloudWatch Logs"]
        SecurityHub["Security Hub"]
    end

    GitRepo -->|Push| CodePipeline
    CodePipeline -->|Trigger| GuardLint
    GuardLint -->|Pass/Fail| CodeBuild
    CodeBuild -->|Deploy| Templates
    Templates -->|Validation| HooksPolicy
    HooksPolicy -->|Evaluate| HooksValidation
    HooksValidation -->|Custom Rules| CustomHooks
    CustomHooks -->|Result| Deployment
    Deployment -->|Notify| SNS
    Deployment -->|Logs| CloudWatch
    CloudWatch -->|Aggregate| SecurityHub
    
    style HooksPolicy fill:#ff9999
    style GuardLint fill:#99ccff
    style HooksValidation fill:#99ff99
    style CustomHooks fill:#ffcc99

CloudFormation Hooksの実装—組織レベルの強制

まずHooksから始めました。Control TowerのLanding Zoneで基本的なセキュリティ設定はされてるんですが、CloudFormationレベルでもう一層の統制をかけたかった。

Hooksを有効化した流れは、AWS Organizations > Policies で CloudFormation Hooks Policy を作成して、Control Tower > Customizations で Hooks ルールを展開。そうすると各アカウントで自動的に Hooks が有効化される。

実装例として、うちが適用してるルールセット(2026年時点での公開ルール)がこちら:

ルール内容検出件数/月
AwsProposedModifications非推奨リソース・設定の警告15〜20件
EncryptionEnabledEBS・RDS・S3暗号化の必須化8〜12件
IamPolicyBlacklist過度なIAM権限の検出3〜5件
SecurityGroupIngressRestrictionセキュリティグループの0.0.0.0/0チェック10〜15件
RequiredTags必須タグの検証5〜8件

で、実際に導入して気づいたことが、Hooksって「ブロック」か「警告」の二択しかないんです。「これは本当に重大」「これは推奨」みたいなグラデーションがない。だからHooksで全部をキャッチしようとするとFalse Positiveが増えまくって、チームから「毎回ルール変更してるじゃん」って不満が出た。

Hooksの設定例(AWS CLI):

aws cloudformation set-type-configuration \
  --type HOOK \
  --type-name AWS::CloudFormation::Hook::EncryptionEnabled \
  --arn arn:aws:cloudformation:ap-northeast-1:123456789012:hook/arn:aws:cloudformation:ap-northeast-1::type/hook/AWS::CloudFormation::Hook::EncryptionEnabled \
  --configuration "{\"EncryptionSettings\":{\"S3\":{\"Enabled\":true},\"RDS\":{\"Enabled\":true}}}" \
  --region ap-northeast-1

実運用では、組織全体に ENFORCE / WARN を分けて適用しました。

{
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": "cloudformation:*",
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "aws:RequestedRegion": "ap-northeast-1"
        }
      }
    },
    {
      "Effect": "Deny",
      "Principal": "*",
      "Action": "cloudformation:CreateStack",
      "Resource": "*",
      "Condition": {
        "StringLike": {
          "cloudformation:StackPolicyParameter": "*AllowPublicAccess*"
        }
      }
    }
  ]
}

CloudFormation Guardの実装—カスタムルール化

Hooksだけだと満足度50%くらいだったので、Guardで業界ガイドラインとか内部基準を具体化しました。

Guardのセットアップは意外と簡単だった。ローカルマシンにcfn-guardをインストールして、テンプレートに対してルールを評価する流れです。

インストール

cargo install cfn-guard

Guard Rulesの例(うちのチームが実装したやつ):

# S3バケットのブロックパブリックアクセス設定を必須化
rule s3_block_public_access_enabled {
    resources.*[ type == 'AWS::S3::Bucket' ] {
        properties.PublicAccessBlockConfiguration exists
        properties.PublicAccessBlockConfiguration.BlockPublicAcls == true
        properties.PublicAccessBlockConfiguration.BlockPublicPolicy == true
        properties.PublicAccessBlockConfiguration.IgnorePublicAcls == true
        properties.PublicAccessBlockConfiguration.RestrictPublicBuckets == true
    }
}

# RDSインスタンスの暗号化を必須化
rule rds_encryption_enabled {
    resources.*[ type == 'AWS::RDS::DBInstance' ] {
        properties.StorageEncrypted == true
        properties.KmsKeyId exists
    }
}

# Lambda関数のReserved Concurrent Executionsを設定
rule lambda_reserved_concurrency {
    resources.*[ type == 'AWS::Lambda::Function' ] {
        properties.ReservedConcurrentExecutions exists
        properties.ReservedConcurrentExecutions > 0
    }
}

# VPCの有効なセキュリティグループ設定
rule vpc_security_group_no_public_ingress {
    resources.*[ type == 'AWS::EC2::SecurityGroup' ] {
        properties.SecurityGroupIngress[*] {
            when CidrIp exists {
                CidrIp != '0.0.0.0/0'
            }
        }
    }
}

# CloudTrail有効化の確認
rule cloudtrail_enabled {
    resources.*[ type == 'AWS::CloudTrail::Trail' ] {
        properties.IsLogging == true
        properties.S3BucketName exists
    }
}

これらのルールをCI/CDパイプラインに統合してます。毎回CodeBuildでチェック。

CodeBuild buildspec.yml の例

version: 0.2

phases:
  install:
    commands:
      - echo "Installing cfn-guard and cfn-lint..."
      - cargo install cfn-guard
      - pip install cfn-lint
  
  pre_build:
    commands:
      - echo "Running cfn-lint..."
      - cfn-lint templates/*.yaml --format json | tee cfn-lint-output.json
      - echo "Running CloudFormation Guard..."
      - cfn-guard validate -t templates/*.yaml -r guard-rules/ --output json > guard-output.json
  
  build:
    commands:
      - |
        if [ $? -eq 0 ]; then
          echo "All validations passed!"
        else
          echo "Validation failed!"
          exit 1
        fi
  
  post_build:
    commands:
      - echo "Uploading validation reports..."
      - aws s3 cp cfn-lint-output.json s3://audit-bucket/cfn-lint/
      - aws s3 cp guard-output.json s3://audit-bucket/guard/

artifacts:
  files:
    - cfn-lint-output.json
    - guard-output.json

これを運用してみて気づいたんですが、ルールを厳しくしすぎると開発者から恨まれます。最初、暗号化・タグ・ロギングは絶対ルールにしたんですが、フィーチャーブランチでの実験的なスタック作成に引っかかりすぎて。結果、本番スタック用と実験用の2つのルールセットに分けました。

# 本番用(厳しい)
cfn-guard validate -t templates/prod/*.yaml -r guard-rules/strict.guard

# 実験用(緩い)
cfn-guard validate -t templates/experimental/*.yaml -r guard-rules/relaxed.guard

検出実績と改善

運用6ヶ月で、Hooksとguardの組み合わせで月50〜60件の設定ミスを検出してます。実装前後で比較すると、かなり改善された。

xychart-beta
    title CloudFormation デプロイ前のセキュリティ違反検出数
    x-axis [1月, 2月, 3月, 4月, 5月, 6月]
    y-axis "検出件数" 0 --> 80
    line [0, 12, 38, 52, 58, 56]
    line [0, 0, 0, 0, 0, 0] title "本番デプロイ後の違反(修正前)"

導入前だと本番デプロイ後にセキュリティ違反が発覚→緊急修正が月3〜5回。導入後はデプロイ前にキャッチできるから、本番デプロイ後の違反はほぼゼロになりました。

コスト面でも、デプロイ後の修正コスト(調査・対応・テスト・再デプロイ)が月平均8〜10時間削減されてます。

ハマった話と解決方法

1. False Positive地獄

最初、Guardのルールが多すぎてFalse Positiveが出まくった。「このテンプレートは意図的に全開放にしてるテスト用なんだけど…」みたいなケースがね。

解決はルールに除外条件を追加することでした。テンプレートのメタデータで「このスタックは検証を除外」とマークできるようにしたんです。

AWSTemplateFormatVersion: '2010-09-09'
Description: 'Test template for security posture validation'
Metadata:
  cfn-guard-exemption: |
    rule: security_group_public_ingress_exempt
    reason: "This is a test environment for staging"
    approved-by: "security-team"
    expires: "2026-12-31"

2. クロスアカウント参照の失敗

Hooksは評価対象テンプレート内のリソースしか見ないから、別アカウントのIAMロールやセキュリティグループを参照してる場合、検証失敗する。

解決は参照情報をテンプレートパラメータ化して、事前にチェックするようにしました。または、Guardで「外部参照の場合は検証スキップ」という条件を入れました。

3. 既存スタック互換性の問題

既存の200個以上のスタックが新しいルールに引っかかるんです。HooksをいきなりENFORCE設定にすると、スタック更新ができなくなる。地味に怖い。

解決は段階的なロールアウトです。最初はWARNでモニタリング。3ヶ月後にENFORCEに切り替え。その間に既存スタックを修正しました。

# 1ヶ月目: WARN で運用
aws cloudformation set-hook-status \
  --hook-id arn:aws:cloudformation:ap-northeast-1::type/hook/AWS::CloudFormation::Hook::EncryptionEnabled \
  --failure-mode WARN

# 4ヶ月目: ENFORCE に切り替え
aws cloudformation set-hook-status \
  --hook-id arn:aws:cloudformation:ap-northeast-1::type/hook/AWS::CloudFormation::Hook::EncryptionEnabled \
  --failure-mode ENFORCE

2026年のベストプラクティス

正直、HooksとGuardを完全に使い分けるのが今のベストプラクティスだと思う。Hooksで「絶対に守るべきルール」を組織レベルで統制。Guardで「チームや部門ごとのカスタムルール」を適用する。

そしてもう一つ重要なのが、Security Hub との統合。CloudFormation Hooks の実行結果をSecurity Hubに集約すると、コンプライアンス態勢がめちゃくちゃ見やすくなります。

# Security Hub統合
aws securityhub create-configuration-aggregator \
  --region ap-northeast-1 \
  --accounts {"Organization": {}}

これでSOC2とかの監査対応もかなり楽になりました。検証結果が自動的に記録されるから、「いつ・どのリソースが・何をチェックされたのか」が全部トレーサブルになります。

まとめ

  • CloudFormation Hooks は組織レベルの強制ツール。公開ルールセットで基本的なセキュリティ・コンプライアンスをガード。ただし柔軟性は低いのが課題。

  • CloudFormation Guard はカスタムルール化ツール。業務要件に合わせた細かいルールを書ける。CI/CDパイプラインへの統合も簡単で、実装の敷居も低い。

  • 二階建て運用で現実的な落としどころに。Hooks で必須ルール、Guard で推奨ルール、という使い分けが実務的。

  • 段階的なロールアウトは必須。いきなり ENFORCE すると既存スタックが死ぬ。WARN でモニタリング→修正→ENFORCE の流れは必ず守った方がいい。

  • False Positive との付き合い方が肝。除外条件やテンプレートメタデータで例外を管理しないと、開発者の信頼を失う。

設定ミスによる本番障害が月3〜5件から月0件になった。これだけでも導入価値があるかな。検証オーバーヘッドもビルド時間で+1〜2分程度。十分許容範囲です。

CloudTrail・Config監査設計で失敗した話。SOC2審査3ヶ月の地獄から学んだ実装パターン も合わせて読むと、IaC検証の全体像が見えやすいと思います。

U

Untanbaby

ソフトウェアエンジニア|AWS / クラウドアーキテクチャ / DevOps

10年以上のIT実務経験をもとに、現場で使える技術情報を発信しています。 記事の誤りや改善点があればお問い合わせからお気軽にご連絡ください。

関連記事