cfn-guard lets you check your CloudFormation templates against custom policies before you deploy them, catching compliance issues that CloudFormation itself would never see.

Imagine you’ve got a new service that needs to be deployed. You’ve written your template.yaml, and it looks good. But before it goes live, you need to make sure it adheres to your company’s security standards: maybe all S3 buckets must have versioning enabled, or no public read access is allowed on any EC2 security groups. CloudFormation itself doesn’t enforce these kinds of organizational policies. That’s where cfn-guard comes in. It’s a policy-as-code tool that lets you define rules in a human-readable language and then apply them to your CloudFormation, Terraform, or Kustomize configurations.

Let’s see it in action. First, you need to install cfn-guard. On macOS, you can use Homebrew:

brew install cfn-guard

Now, let’s define a simple policy. Create a file named policies.guard:

# policies.guard
allow {
  # All S3 buckets must have versioning enabled
  s3bucket.versioning.enabled == true
}

allow {
  # No public read access on EC2 security groups
  not ec2securitygroup.ingress.cidr_ip contains "0.0.0.0/0"
}

# If neither of the above 'allow' rules apply, the resource is denied.

This policy has two allow rules. The first checks if an S3 bucket resource has versioning.enabled set to true. The second checks if an EC2 security group resource has any ingress rule that allows traffic from 0.0.0.0/0 (meaning public access). If a resource doesn’t satisfy either of these allow rules, it’s considered a violation.

Now, let’s create a sample CloudFormation template, template.yaml, that we want to test:

# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: A sample template

Resources:
  MyS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-secure-bucket-12345

  MyPublicS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-insecure-bucket-67890

  MySecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: MyWebServerSG
      GroupDescription: Allow HTTP and HTTPS access
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0

  MyPrivateSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: MyInternalSG
      GroupDescription: Internal access only
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 10.0.0.0/16

Notice that MyS3Bucket and MyPrivateSecurityGroup should pass the policy, while MyPublicS3Bucket and MySecurityGroup should fail.

To run the validation, you use the guard command:

guard validate --data policies.guard --template template.yaml

Here’s what the output might look like if there are violations:

FAIL: Resource MyPublicS3Bucket (AWS::S3::Bucket) failed validation:
  - Rule: Rule s3bucket.versioning.enabled == true failed on resource MyPublicS3Bucket
FAIL: Resource MySecurityGroup (AWS::EC2::SecurityGroup) failed validation:
  - Rule: Rule not ec2securitygroup.ingress.cidr_ip contains "0.0.0.0/0" failed on resource MySecurityGroup

The output clearly tells you which resources failed and which specific rule they violated. You can then go back and fix your template.yaml. For example, to fix MyS3Bucket, you’d add VersioningConfiguration: Enabled: true to its properties. To fix MySecurityGroup, you’d remove the ingress rules allowing 0.0.0.0/0.

The power of cfn-guard lies in its expressive policy language. You can define complex rules that go far beyond what AWS CloudFormation or Terraform’s built-in validation can offer. It models resources and their properties in a structured way, allowing you to query specific attributes. For instance, you can check for specific tags, ensure certain instance types are not used, or verify that specific IAM permissions are not granted. The guard validate command parses your infrastructure-as-code file, maps its resources to cfn-guard’s internal model, and then evaluates each resource against your defined policies.

One subtle but powerful aspect of cfn-guard is how it handles lists and complex nested properties. When you write ec2securitygroup.ingress.cidr_ip contains "0.0.0.0/0", cfn-guard automatically understands that ingress is a list of rules, and for each rule in that list, it checks the cidr_ip property. If any of the cidr_ip values in any of the ingress rules is "0.0.0.0/0", the condition evaluates to true. This deep inspection into nested structures and collections is crucial for writing effective security and compliance policies.

After successfully validating your template with cfn-guard, the next step in a CI/CD pipeline would typically be to upload the template to an S3 bucket or an artifact repository, ready for deployment.

Want structured learning?

Take the full Cloudformation course →