- Context
- Guard rules and Cloudformation Guard hook
- Setting Up CloudFormation Hook using Terraform
- Test the rules
- Best Practices
- Conclusion
Context
Organizations using Infrastructure as Code (IaC) often face challenges like maintaining consistent security configurations across multiple deployments, ensuring compliance with organizational policies, and preventing misconfigurations that could lead to security vulnerabilities.Security guard rails help mitigate these challenges by automating policy enforcement, providing pre-deployment validation checks, and ensuring that all infrastructure changes adhere to security best practices and compliance requirements before they're deployed, thereby reducing risk and maintaining consistent security standards across the organization. In this post, we'll demonstrate how to implement CloudFormation Guard hooks to enforce some S3 bucket requirements using AWSCC provider for deploying the hooks as well.
Guard rules and Cloudformation Guard hook
CloudFormation Guard (cfn-guard) is a policy-as-code tool provided by AWS that allows you to define rules to prevent deployment of non-compliant infrastructure. These rules are written in a domain-specific language (Guard DSL) and can check for security best practices, compliance requirements, and organizational standards. For example, you can create rules to ensure all S3 buckets are encrypted, EC2 instances use specific instance types, or resources have required tags. Guard rules help enforce governance at scale by catching policy violations before resources are deployed in your AWS environment.
A CloudFormation Guard hook is a feature that integrates Guard rules directly into the deployment process using AWS CloudFormation Hooks. It automatically evaluates your Guard rules during stack operations (create, update, or delete) and can prevent non-compliant infrastructure changes from being deployed.
Key points about CloudFormation Guard hooks:
- They run pre-deployment validation checks
- Can block operations if rules are violated
- Help enforce compliance automatically during infrastructure deployment
- Can be configured at the organization level using AWS Organizations
This creates a proactive compliance mechanism rather than having to run Guard rules manually or through separate processes.
Guard Rule Configuration
Create a file safebucket.guard
with the following content:
# Rule Identifier:
# S3_BUCKET_PUBLIC_WRITE_PROHIBITED
let s3_buckets_public_write_prohibited = Resources.*[ Type == 'AWS::S3::Bucket' ]
rule S3_BUCKET_PUBLIC_WRITE_PROHIBITED when %s3_buckets_public_write_prohibited !empty {
%s3_buckets_public_write_prohibited.Properties.PublicAccessBlockConfiguration exists
%s3_buckets_public_write_prohibited.Properties.PublicAccessBlockConfiguration.BlockPublicAcls == true
%s3_buckets_public_write_prohibited.Properties.PublicAccessBlockConfiguration.BlockPublicPolicy == true
%s3_buckets_public_write_prohibited.Properties.PublicAccessBlockConfiguration.IgnorePublicAcls == true
%s3_buckets_public_write_prohibited.Properties.PublicAccessBlockConfiguration.RestrictPublicBuckets == true
<<
Violation: S3 Bucket public write access must be restricted.
Fix: Set the S3 Bucket PublicAccessBlockConfiguration properties—BlockPublicAcls, BlockPublicPolicy, IgnorePublicAcls, RestrictPublicBuckets—to true.
>>
}
# Rule Identifier:
# S3_BUCKET_VERSIONING_ENABLED
let s3_buckets_versioning_enabled = Resources.*[ Type == 'AWS::S3::Bucket' ]
rule S3_BUCKET_VERSIONING_ENABLED when %s3_buckets_versioning_enabled !empty {
%s3_buckets_versioning_enabled.Properties.VersioningConfiguration exists
%s3_buckets_versioning_enabled.Properties.VersioningConfiguration.Status == 'Enabled'
<<
Violation: S3 Bucket versioning must be enabled.
Fix: Set the S3 Bucket property VersioningConfiguration.Status to 'Enabled'.
>>
}
Setting Up CloudFormation Hook using Terraform
First, let's create the CloudFormation hook configuration using Terraform and the AWSCC provider.
We are currently deploying a few components of the Hooks configuration:
- S3 bucket to hold the guard rules.
- S3 bucket to direct the guard rule execution logs
- Guard hooks under the alias
CCAPI::S3::Hooks
. - IAM role required for hooks.
You can swap out your existing S3 bucket for the configuration, if needed.
resource "aws_s3_bucket" "hooks" {
bucket = "ccapihooks"
}
resource "aws_s3_bucket" "hooks_logs" {
bucket = "ccapihooks_logs"
}
resource "aws_s3_object" "hooks" {
bucket = aws_s3_bucket.hooks.id
key = "safebucket.guard"
source = "safebucket.guard"
etag = filemd5("safebucket.guard")
}
resource "awscc_cloudformation_guard_hook" "example" {
alias = "CCAPI::S3::Hooks"
rule_location = {
uri = "s3://${aws_s3_bucket.hooks.id}/${aws_s3_object.hooks.key}"
}
execution_role = awscc_iam_role.example.arn
failure_mode = "FAIL"
target_operations = ["CLOUD_CONTROL"]
hook_status = "ENABLED"
target_filters = {
actions = ["CREATE", "UPDATE"]
invocation_points = ["PRE_PROVISION"]
target_names = ["AWS::S3::Bucket"]
}
}
resource "awscc_iam_role" "example" {
path = "/"
assume_role_policy_document = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "hooks.cloudformation.amazonaws.com"
}
},
]
})
policies = [{
policy_name = "allow_s3_access"
policy_document = jsonencode(
{
Version = "2012-10-17"
Statement = [
{
Action = [
"s3:GetObject",
"s3:GetObjectVersion",
"s3:ListBucket",
"s3:PutObject"
]
Effect = "Allow"
Resource = [
"arn:aws:s3:::${aws_s3_bucket.hooks.id}",
"arn:aws:s3:::${aws_s3_bucket.hooks.id}/*",
"arn:aws:s3:::${aws_s3_bucket.hooks_logs.id}",
"arn:aws:s3:::${aws_s3_bucket.hooks_logs.id}/*"
]
},
]
}
)
}]
}
Now, let's deploy the configuration into a target account.This should be familiar to you from any Terraform workflow you have performed.
terraform init
terraform apply --auto-approve
Once the hook configuration is deployed, they should be available in your CloudFormation registry as a Hook with a target specified based on your configuration.
Test the rules
Failed S3 Bucket Configuration
Here's an example of an S3 bucket configuration that would fail the guard rules:
resource "awscc_s3_bucket" "failing_bucket" {}
When you try to apply this configuration using terraform apply
, the CloudFormation hook will reject it with an error similar to:
│ Error: AWS SDK Go Service Operation Incomplete
│
│ with awscc_s3_bucket.failing_bucket,
│ on s3_fail.tf line 1, in resource "awscc_s3_bucket" "failing_bucket":
│ 1: resource "awscc_s3_bucket" "failing_bucket" {
│ Waiting for Cloud Control API service CreateResource operation completion returned: waiter state transitioned to FAILED. StatusMessage:
│ 87a50e1e-6351-4118-a909-9532834ad8c4. ErrorCode:
On further evaluation of the results using the aws cli:
aws cloudformation list-hook-results --target-type CLOUD_CONTROL --target-id 87a50e1e-6351-4118-a909-9532834ad8c4
{
"TargetType": "CLOUD_CONTROL",
"TargetId": "87a50e1e-6351-4118-a909-9532834ad8c4",
"HookResults": [
{
"InvocationPoint": "PRE_PROVISION",
"FailureMode": "FAIL",
"TypeName": "CCAPI::S3::Hooks",
"TypeVersionId": "00000020",
"Status": "HOOK_COMPLETE_FAILED",
"HookStatusReason": "Template failed validation, the following rule(s) failed: S3_BUCKET_PUBLIC_WRITE_PROHIBITED, S3_BUCKET_VERSIONING_ENABLED."
}
]
}
Compliant S3 Bucket Configuration
Here's the corrected version that passes the guard rules:
# Create compliant S3 bucket
resource "awscc_s3_bucket" "compliant_bucket" {
versioning_configuration = {
status = "Enabled"
}
public_access_block_configuration = {
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
}
The response from the terraform apply
should be along the lines below.
Plan: 1 to add, 0 to change, 0 to destroy.
awscc_s3_bucket.compliant_bucket: Creating...
awscc_s3_bucket.compliant_bucket: Creation complete after 25s [id=vq6p47wtyolfg1fygcio8mxsy-jldlgfyilyix]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Execution logs
You can review the execution logs in the output S3 bucket we have provisioned using the Hooks infrastructure.
- Path :
cfn-guard-validate-report/<resource_name>-<request_id>-RESOURCE-<resource_name>-<stage>
- The one from our execution is present on
cfn-guard-validate-report/AWS--S3--Bucket-87a50e1e-6351-4118-a909-9532834ad8c4-RESOURCE-AWS--S3--Bucket-CREATE-PRE_PROVISION
.
A trimmed down version of the results and rationale for failure in the output json is shown below.
"Clause": {
"Unary": {
"check": {
"UnResolved": {
"value": {
"traversed_to": {
"path": "/Resources/87a50e1e-6351-4118-a909-9532834ad8c4-RESOURCE-AWS::S3::Bucket-CREATE-PRE_PROVISION/Properties",
"value": {}
},
"remaining_query": "PublicAccessBlockConfiguration",
"reason": "Could not find key PublicAccessBlockConfiguration inside struct at path /Resources/87a50e1e-6351-4118-a909-9532834ad8c4-RESOURCE-AWS::S3::Bucket-CREATE-PRE_PROVISION/Properties[L:0,C:138]"
},
"comparison": [
"Exists",
false
]
}
},
"context": " %s3_buckets_public_write_prohibited[*].Properties.PublicAccessBlockConfiguration EXISTS ",
"messages": {
"custom_message": "",
"error_message": "Check was not compliant as property [PublicAccessBlockConfiguration] is missing. Value traversed to [Path=/Resources/87a50e1e-6351-4118-a909-9532834ad8c4-RESOURCE-AWS::S3::Bucket-CREATE-PRE_PROVISION/Properties[L:0,C:138] Value={}]."
}
}
}
Best Practices
- Testing: Always test hooks in a non-production environment first before promoting them to a higher environment.
- Versioning: Version control your guard rules using a git repository to enable collaboration and audit.
- Documentation: Document the purpose and requirements of each rule.
Conclusion
CloudFormation hooks provide a powerful way to enforce security and compliance requirements in your infrastructure. By combining them with Terraform's AWSCC provider, you can ensure that all infrastructure changes deployed using the provider meet your organization's security standards before they're applied.
Top comments (0)