What You'll Build
In this lab you will deploy a complete AWS Config auto-remediation pipeline using Terraform. The pipeline detects when an S3 bucket's public access block is removed, automatically triggers an SSM Automation document that re-enables the block, and restores compliance without human intervention. You will verify the pipeline by deliberately making a test bucket public and watching Config detect the violation and SSM Automation remediate it within minutes.
By completing this lab, you will have a production-ready Config rule and SSM Automation document deployed as infrastructure-as-code, tested end-to-end, and ready to protect all india-squad S3 buckets against accidental public exposure. The complete pipeline—detect, remediate, verify—runs automatically within minutes of a misconfiguration occurring, substantially faster than any human monitoring and response process could achieve.
Prerequisites
- Terraform >= 1.5 installed locally and configured with AWS credentials that have Config:*, SSM:*, S3:*, and IAM:* permissions.
- AWS Config not yet enabled in the account, or enabled without a delivery channel—the lab creates its own Config recorder and delivery channel.
- The AWS account from Exercise 27 with Security Hub enabled, so the Config rule findings appear in the Security Hub dashboard alongside other findings.
- An S3 bucket available for testing that can be temporarily made public and then restored—the lab creates a dedicated test bucket for this purpose.
- Familiarity with Terraform apply and output commands from previous infrastructure exercises.
Setup — Project Scaffold and Test Bucket
Create the Terraform project directory, write the provider configuration, and deploy a tagged test S3 bucket. The bucket's `Project: india-squad` tag is what the Config rule will use to scope its evaluation—without this tag, the rule will not evaluate the bucket, allowing you to test the specific bucket that you control without affecting other account buckets.
# Prerequisites: AWS account with Security Hub enabled from Exercise 27.
# We will deploy Config rule + SSM Automation via Terraform.
mkdir india-squad-config && cd india-squad-config
cat > main.tf << 'EOF'
terraform {
required_providers {
aws = { source = 'hashicorp/aws', version = '~> 5.0' }
}
}
provider 'aws' { region = 'ap-south-1' }
data 'aws_caller_identity' 'current' {}
data 'aws_region' 'current' {}
EOF
# Initialise Terraform
terraform init
# Create a test S3 bucket to verify auto-remediation later
cat > test-bucket.tf << 'EOF'
resource 'aws_s3_bucket' 'india_squad_test' {
bucket = 'india-squad-config-test-${data.aws_caller_identity.current.account_id}'
tags = { Project = 'india-squad', Environment = 'test' }
}
# Output the bucket name for use in verification steps
output 'test_bucket_name' {
value = aws_s3_bucket.india_squad_test.id
}
EOF
terraform apply -auto-approve
export TEST_BUCKET=$(terraform output -raw test_bucket_name)
echo "Test bucket: $TEST_BUCKET"Step 1 — Deploy the Config Rule
Create the Config recorder, delivery channel, IAM role, and the managed Config rule `S3_BUCKET_LEVEL_PUBLIC_ACCESS_PROHIBITED`. The Config recorder must be running before the managed rule can evaluate resources. The delivery channel requires an S3 bucket for Config to store its snapshots and history; create a dedicated Config logs bucket separate from the test bucket.
# Step 1: Deploy the Config rule using Terraform.
cat > config-rule.tf << 'EOF'
# IAM role for Config to use when evaluating rules
resource 'aws_iam_role' 'config' {
name = 'india-squad-config-role'
assume_role_policy = jsonencode({
Version = '2012-10-17'
Statement = [{
Action = 'sts:AssumeRole'
Effect = 'Allow'
Principal = { Service = 'config.amazonaws.com' }
}]
})
}
resource 'aws_iam_role_policy_attachment' 'config_readonly' {
role = aws_iam_role.config.name
policy_arn = 'arn:aws:iam::aws:policy/ReadOnlyAccess'
}
# Config recorder (required for managed rules)
resource 'aws_config_configuration_recorder' 'india_squad' {
name = 'india-squad'
role_arn = aws_iam_role.config.arn
recording_group { all_supported = true }
}
resource 'aws_config_configuration_recorder_status' 'india_squad' {
name = aws_config_configuration_recorder.india_squad.name
is_enabled = true
depends_on = [aws_config_delivery_channel.india_squad]
}
# Delivery channel (required for Config to function)
resource 'aws_config_delivery_channel' 'india_squad' {
name = 'india-squad'
s3_bucket_name = aws_s3_bucket.config_logs.id
depends_on = [aws_config_configuration_recorder.india_squad]
}
resource 'aws_s3_bucket' 'config_logs' {
bucket = 'india-squad-config-logs-${data.aws_caller_identity.current.account_id}'
}
# The Config rule: detect S3 buckets without block public access
resource 'aws_config_config_rule' 's3_block_public_access' {
name = 'india-squad-s3-block-public-access'
depends_on = [aws_config_configuration_recorder.india_squad]
source {
owner = 'AWS'
source_identifier = 'S3_BUCKET_LEVEL_PUBLIC_ACCESS_PROHIBITED'
}
}
EOF
terraform apply -auto-approve
# Verify the Config rule was created:
aws configservice describe-config-rules \
--config-rule-names india-squad-s3-block-public-access \
--query 'ConfigRules[0].{Name:ConfigRuleName,State:ConfigRuleState}'Step 2 — Create the SSM Automation Document
Write the SSM Automation document that performs the remediation: a single-step automation that calls the S3 `put_public_access_block` API with all four block settings enabled. Create the IAM role that SSM Automation will assume when executing the document, scoped to the minimum permission needed: `s3:PutBucketPublicAccessBlock` only. This least-privilege role prevents the remediation automation from performing any action beyond its specific purpose.
# Step 2: Create the SSM Automation remediation document.
cat > ssm-remediation.tf << 'EOF'
# IAM role for SSM Automation to use when remediating
resource 'aws_iam_role' 'config_remediation' {
name = 'india-squad-config-remediation'
assume_role_policy = jsonencode({
Version = '2012-10-17'
Statement = [{
Action = 'sts:AssumeRole'
Effect = 'Allow'
Principal = { Service = 'ssm.amazonaws.com' }
}]
})
}
resource 'aws_iam_role_policy' 'remediation_s3' {
role = aws_iam_role.config_remediation.id
policy = jsonencode({
Version = '2012-10-17'
Statement = [{
Effect = 'Allow'
Action = 's3:PutBucketPublicAccessBlock'
Resource = 'arn:aws:s3:::*'
}]
})
}
# SSM Automation document that enables S3 Block Public Access
resource 'aws_ssm_document' 'block_s3_public' {
name = 'india-squad-BlockS3PublicAccess'
document_type = 'Automation'
content = jsonencode({
schemaVersion = '0.3'
description = 'Enable S3 Block Public Access on a non-compliant bucket'
assumeRole = '{{ AutomationAssumeRole }}'
parameters = {
BucketName = { type = 'String' }
AutomationAssumeRole = { type = 'String', default = '' }
}
mainSteps = [{
name = 'BlockPublicAccess'
action = 'aws:executeAwsApi'
inputs = {
Service = 's3'
Api = 'put_public_access_block'
Bucket = '{{ BucketName }}'
PublicAccessBlockConfiguration = {
BlockPublicAcls = true
IgnorePublicAcls = true
BlockPublicPolicy = true
RestrictPublicBuckets = true
}
}
}]
})
}
EOF
terraform apply -auto-approveStep 3 — Link Config Rule to SSM Automation
Add the `aws_config_remediation_configuration` resource that links the Config rule to the SSM document and enables automatic execution. The `RESOURCE_ID` parameter substitution instructs Config to pass the non-compliant bucket name as the `BucketName` parameter to SSM Automation automatically—no manual parameter passing is required. Apply the Terraform configuration to deploy the complete remediation pipeline.
# Step 3: Link the Config rule to the SSM document for auto-remediation.
cat >> config-rule.tf << 'EOF'
# Link the Config rule to the SSM document for automatic remediation
resource 'aws_config_remediation_configuration' 's3_block_public' {
config_rule_name = aws_config_config_rule.s3_block_public_access.name
resource_type = 'AWS::S3::Bucket'
target_type = 'SSM_DOCUMENT'
target_id = aws_ssm_document.block_s3_public.name
automatic = true
maximum_automatic_attempts = 5
retry_attempt_seconds = 60
parameter {
name = 'BucketName'
resource_value = 'RESOURCE_ID' # Config passes the non-compliant bucket name
}
parameter {
name = 'AutomationAssumeRole'
static_value = aws_iam_role.config_remediation.arn
}
depends_on = [aws_config_config_rule.s3_block_public_access]
}
EOF
terraform apply -auto-approveStep 4 — Test and Verify
Delete the test bucket's public access block configuration to simulate a misconfiguration, wait for Config to detect the non-compliance, watch SSM Automation execute the remediation, and verify the bucket is back to compliant. The full cycle from misconfiguration to remediation typically takes 3-5 minutes—substantially faster than any human detection and response process.
# Step 4: Trigger the auto-remediation by making the test bucket public.
# Make the test bucket public (removes block public access) — this triggers Config
aws s3api delete-public-access-block \
--bucket $TEST_BUCKET
# Verify the bucket is now public (block access removed)
aws s3api get-public-access-block --bucket $TEST_BUCKET 2>&1
# Expected: NoSuchPublicAccessBlockConfiguration (the block config was deleted)
# Wait 2-3 minutes for Config to detect the change and trigger SSM Automation
echo 'Waiting for Config to detect non-compliance...'
sleep 180
# Check Config rule evaluation result
aws configservice get-compliance-details-by-config-rule \
--config-rule-name india-squad-s3-block-public-access \
--compliance-types NON_COMPLIANT \
--query 'EvaluationResults[*].{Resource:EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId,Compliance:ComplianceType}'
# Check SSM Automation execution history
aws ssm list-automation-executions \
--filters 'Key=DocumentName,Values=india-squad-BlockS3PublicAccess' \
--query 'AutomationExecutionMetadataList[0].{Status:AutomationExecutionStatus,Start:ExecutionStartTime}'
# Wait for remediation to complete (another 1-2 minutes)
sleep 120
# Verify the bucket has been automatically remediated
aws s3api get-public-access-block --bucket $TEST_BUCKET
# Expected: all four fields true — auto-remediation restored block public access
# Confirm the Config rule now shows COMPLIANT for this bucket
aws configservice get-compliance-details-by-config-rule \
--config-rule-name india-squad-s3-block-public-access \
--compliance-types COMPLIANT \
--query 'EvaluationResults[*].EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId'Warning: The `automatic: true` setting on the remediation configuration means Config will execute the SSM Automation document without any manual approval for every non-compliant bucket that matches the rule's scope. Before enabling automatic remediation in a production account, verify that none of the in-scope buckets have a legitimate business need for public access—such as a static website hosting bucket. For buckets that must remain public, either exclude them from the rule's scope by removing the Project tag or add a rule exception. Test automatic remediation in a non-production account first before enabling it in production.
Extension Challenge: Extend the lab by adding a second Config rule that detects S3 buckets without server-side encryption enabled and an SSM Automation document that enables SSE-KMS encryption using the india-squad CMK from Lesson 23. Chain the two remediation configurations so that Config automatically enables both block public access and encryption on any newly created S3 bucket, ensuring all india-squad S3 buckets meet the CIS Level 2 encryption standard from the moment they are created.