DEV Community

quixoticmonk
quixoticmonk

Posted on

Secure Terraform AWSCC provider resources with Guard Hooks

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:

  1. They run pre-deployment validation checks
  2. Can block operations if rules are violated
  3. Help enforce compliance automatically during infrastructure deployment
  4. 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'.
  >>
}
Enter fullscreen mode Exit fullscreen mode

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}/*"
            ]
          },
        ]
      }
    )
  }]
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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" {}
Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode

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."
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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={}]."
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  1. Testing: Always test hooks in a non-production environment first before promoting them to a higher environment.
  2. Versioning: Version control your guard rules using a git repository to enable collaboration and audit.
  3. 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)