advanced 45 min read aws Updated: 2025-07-15

AWS CloudFormation Guard Policies Complete Guide

Write and deploy CloudFormation Guard rules for infrastructure validation, security enforcement, and compliance checking in your AWS deployments.

๐Ÿ“‹ Prerequisites

  • Expert knowledge of Infrastructure as Code (CloudFormation or Terraform).
  • Advanced proficiency with YAML/JSON syntax and structure.
  • Strong experience with CI/CD concepts and tooling (e.g., GitHub Actions, AWS CodePipeline).
  • Familiarity with the command line and installing open-source tools.

๐Ÿ’ก From Detective to Preventive: The "Shift-Left" Revolution

While detective controls like AWS Config are essential for monitoring deployed resources, **preventive controls** stop non-compliant infrastructure from ever being created. CloudFormation Guard is a powerful open-source tool that allows you to validate IaC templates against codified policies, embedding security and compliance directly into your development workflow and CI/CD pipeline. This is the essence of "shifting left."

๐Ÿท๏ธ Topics Covered

aws cloudformation guard tutorialcloudformation guard rules examplesaws infrastructure validationcloudformation security policiesaws guard rule syntaxcloudformation template validationaws infrastructure compliancecloudformation guard best practices

Writing Advanced Guard Rules

CloudFormation Guard uses a declarative, domain-specific language that is easy to read but powerful enough to express complex compliance logic. Let's explore some advanced rule patterns.

๐Ÿ›ก๏ธ Guard: Comprehensive S3 Bucket Security Rule

This rule ensures that all S3 buckets follow security best practices: encryption, blocked public access, and versioning must all be enabled.

#
# Rule: S3_BUCKET_SECURE_CONFIGURATION
#
let s3_buckets = Resources.*[ Type == 'AWS::S3::Bucket' ]

rule S3_BUCKET_SECURE_CONFIGURATION when %s3_buckets !empty {
    %s3_buckets.Properties {
        # Check 1: Server-Side Encryption must be enabled
        BucketEncryption.ServerSideEncryptionConfiguration[*] {
            ServerSideEncryptionByDefault.SSEAlgorithm == "AES256" or
            ServerSideEncryptionByDefault.SSEAlgorithm == "aws:kms"
        } << Must have AES256 or KMS encryption enabled.

        # Check 2: Block Public Access configuration must be set
        PublicAccessBlockConfiguration {
            BlockPublicAcls == true
            BlockPublicPolicy == true
            IgnorePublicAcls == true
            RestrictPublicBuckets == true
        } << Public Access must be blocked on all four settings.

        # Check 3: Versioning must be enabled
        VersioningConfiguration.Status == "Enabled" << Versioning must be enabled for data protection.
    }
}

๐ŸŒ Guard: Secure Security Group Ingress Rule

A common enterprise requirement is to deny unrestricted ingress (`0.0.0.0/0`) and ensure that SSH (port 22) is only open to a specific bastion host IP. This rule uses variables (`let`) and complex queries.

let security_groups = Resources.*[ Type == 'AWS::EC2::SecurityGroup' ]

# Define an allowed CIDR for SSH access
let allowed_ssh_cidr = '10.10.0.5/32' # Bastion Host IP

rule SECURE_INGRESS_RULES when %security_groups !empty {
    %security_groups.Properties.SecurityGroupIngress[*] {
        # Check 1: Deny unrestricted ingress for any protocol
        CidrIp != "0.0.0.0/0" << Unrestricted ingress (0.0.0.0/0) is not allowed.

        # Check 2: Handle specific rules for port 22 (SSH)
        when ToPort == 22 and FromPort == 22 {
            # SSH can only be from the allowed bastion host CIDR
            CidrIp == %allowed_ssh_cidr << SSH access is only permitted from the bastion host.
        }
    }
}

Test-Driven Development (TDD) for Rules

How do you know your rules work correctly without deploying infrastructure? CFN Guard includes a powerful test framework. You write test cases with sample IaC snippets and expected outcomes (`PASS` or `FAIL`).

๐Ÿงช YAML: CFN Guard Test Definition

This test file validates our `SECURE_INGRESS_RULES` rule with both a compliant and a non-compliant Security Group template.

rule-file: ./rules/security_group_rules.guard

tests:
  - name: Test_Compliant_SG
    input:
      Resources:
        MyWebAppSG:
          Type: AWS::EC2::SecurityGroup
          Properties:
            GroupDescription: "Allow SSH from bastion and HTTPS"
            SecurityGroupIngress:
              - IpProtocol: tcp
                FromPort: 22
                ToPort: 22
                CidrIp: 10.10.0.5/32  # Compliant SSH
              - IpProtocol: tcp
                FromPort: 443
                ToPort: 443
                CidrIp: 10.20.0.0/16 # Compliant HTTPS
    expectations:
      rules:
        SECURE_INGRESS_RULES: PASS

  - name: Test_Non_Compliant_SG_Unrestricted_Ingress
    input:
      Resources:
        BadSG:
          Type: AWS::EC2::SecurityGroup
          Properties:
            GroupDescription: "Non-compliant unrestricted ingress"
            SecurityGroupIngress:
              - IpProtocol: tcp
                FromPort: 80
                ToPort: 80
                CidrIp: 0.0.0.0/0 # VIOLATION: Unrestricted ingress
    expectations:
      rules:
        SECURE_INGRESS_RULES: FAIL

Integrating Guard into a CI/CD Pipeline

The ultimate goal of preventive controls is to fail a build or deployment *before* insecure infrastructure is created. This is accomplished by adding a CFN Guard validation step to your CI/CD pipeline.

๐Ÿ”„ YAML: GitHub Actions CI/CD Pipeline with CFN Guard

name: IaC Validation Pipeline

on:
  pull_request:
    branches: [ main ]
    paths:
      - 'templates/**'
      - 'rules/**'

jobs:
  validate-iac:
    name: Validate Infrastructure as Code
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Install AWS CloudFormation Guard
        run: |
          curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/aws-cloudformation/cloudformation-guard/main/install-guard.sh | sh
          echo "/home/runner/.guard/bin" >> $GITHUB_PATH

      - name: Run CFN Guard Validation
        run: |
          cfn-guard validate --data templates/ --rules rules/ --show-summary fail

Managing Rule Sets at Enterprise Scale

For a large organization, managing rules requires a structured approach. A single, monolithic rule file is not scalable or maintainable.

๐Ÿ“ฆ Central Git Repository

Store all Guard rules in a dedicated, version-controlled repository, making it the single source of truth for policies.

๐Ÿ“‚ Logical Grouping

Organize rules into directories by compliance domain (e.g., `/security`, `/cost`, `/tagging`, `/networking`).

๐Ÿ”— Rule Layering

Apply a base set of mandatory rules to all pipelines, and allow teams to layer on additional, more specific rules for their applications.

๐Ÿท๏ธ Versioning

Use Git tags or branches to version your rule sets. CI/CD pipelines can then pull specific, stable versions of the rules for validation.

Troubleshooting Advanced Guard Rules

As rules become more complex, debugging them requires understanding how Guard queries and evaluates data.

โŒ Rule Fails to Trigger on Nested Properties

  • Symptom: A rule that checks a deeply nested property (e.g., a specific container definition in an ECS Task) doesn't seem to evaluate correctly.
  • Cause: The query path to the property is incorrect, or the property may not exist in all resources, causing the rule to fail evaluation.
  • Solution: Use the `cfn-guard parse-tree` command on your template (`cfn-guard parse-tree -t my-template.yaml`). This will print the exact, queryable structure of your template as Guard sees it. You can then use this to verify your query paths. Also, use `when` clauses to ensure your rule only applies when the property you are checking actually exists.

โ” Ambiguous `FAIL` without a Clear Reason

  • Symptom: A rule fails, but the default error message isn't helpful.
  • Cause: The rule lacks custom violation messages.
  • Solution: Add custom messages using the `<< ... >>` syntax after every clause. You can use variables within the message to provide context. For example: `CidrIp != "0.0.0.0/0" << The CIDR for rule '%{this.Description}' cannot be 0.0.0.0/0`.

๐Ÿ”‘ Expert-Level CFN Guard Best Practices

  • Integrate as Early as Possible: Add CFN Guard to pre-commit hooks and Pull Request checks to give developers the fastest possible feedback.
  • Test Your Rules, Not Just Your Templates: Use `cfn-guard test` extensively to validate that your rules behave exactly as you expect before enforcing them.
  • Provide Meaningful Error Messages: Use custom messages ('<< ... >>') in your rules to give developers clear, actionable feedback on why their template failed validation.

You've Mastered Preventive Controls!

You have now completed the entire AWS Governance series. Use these guides to build a secure, compliant, and well-managed cloud environment.