A practical guide to implementing account-level S3 Block Public Access using CloudFormation StackSets
The Challenge
Organizations with multiple AWS accounts need consistent S3 security posture to prevent accidental public exposure of sensitive data. Manual configuration of S3 Block Public Access settings across hundreds of accounts becomes impractical and creates security gaps. Organizations need automated deployment that provides organization-wide protection regardless of individual bucket configurations.
This best practice is supported by CIS AWS Foundations Benchmark v3.0.0 S3.1 control, which requires account-level block public access settings for S3 general purpose buckets.
Account-Level vs Bucket-Level Protection
S3 Block Public Access operates at two levels: account-level and bucket-level. Account-level settings provide superior protection by overriding all bucket-level configurations, policies, and ACLs. This creates a security boundary that prevents public access regardless of individual bucket settings or misconfigurations.
Account-level settings effectively satisfy both CIS S3.1 (account-level) and S3.8 (bucket-level) requirements since the account-level configuration takes precedence.
Implementation Approach
CloudFormation StackSets provide scalable deployment using Lambda-backed custom resources to call the S3 Control API. AWS CloudFormation lacks native support for account-level S3 Block Public Access settings, making custom resources the standard implementation approach. The solution deploys consistently across all accounts and automatically applies to new accounts when configured with automatic deployment.
CloudFormation Template
AWSTemplateFormatVersion: '2010-09-09'
Resources:
S3AccountPublicAccessBlock:
Type: AWS::CloudFormation::CustomResource
Properties:
ServiceToken: !GetAtt S3PublicAccessBlockFunction.Arn
S3PublicAccessBlockFunction:
Type: AWS::Lambda::Function
Properties:
Runtime: python3.12
Handler: index.handler
Role: !GetAtt LambdaExecutionRole.Arn
Code:
ZipFile: |
import boto3
import cfnresponse
def handler(event, context):
try:
s3control = boto3.client('s3control')
account_id = context.invoked_function_arn.split(':')[4]
if event['RequestType'] == 'Create' or event['RequestType'] == 'Update':
s3control.put_public_access_block(
AccountId=account_id,
PublicAccessBlockConfiguration={
'BlockPublicAcls': True,
'IgnorePublicAcls': True,
'BlockPublicPolicy': True,
'RestrictPublicBuckets': True
}
)
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
except Exception as e:
print(f"Error: {str(e)}")
cfnresponse.send(event, context, cfnresponse.FAILED, {})
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: S3ControlPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:PutAccountPublicAccessBlock
- s3:GetAccountPublicAccessBlock
Resource: '*'
Deployment Strategy
Deploy using CloudFormation StackSets from the management account or delegated administrator. Target organizational units based on governance requirements and enable automatic deployment for new accounts.
# Create StackSet
aws cloudformation create-stack-set \
--stack-set-name s3-block-public-access-stackset \
--template-body file://s3-block-public-access.yaml \
--capabilities CAPABILITY_IAM
# Deploy to organization
aws cloudformation create-stack-instances \
--stack-set-name s3-block-public-access-stackset \
--deployment-targets OrganizationalUnitIds=r-org-id \
--regions us-east-1 \
--operation-preferences MaxConcurrentPercentage=100
Operations and Maintenance
Account-level Block Public Access settings are persistent and require no ongoing maintenance once configured. The settings automatically apply to all existing and future S3 buckets in the account, providing comprehensive protection without per-bucket configuration.
AWS Security Hub provides built-in detective controls to monitor accounts without proper S3 Block Public Access configuration through CIS compliance checks.
Limitations and Alternatives
This approach creates Lambda functions in each account, which may be unnecessary overhead for one-time configuration.
For one-time setup, AWS CLI provides simpler deployment without per-account resources:
aws s3control put-public-access-block \
--account-id 123456789012 \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
CloudFormation StackSets provide ongoing management and automatic application to new accounts, while CLI offers simpler one-time deployment for existing accounts.