Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AWS::S3::PublicAccessBlock Account Wide Setting #168

Open
leozhad opened this issue Sep 10, 2019 · 6 comments
Open

AWS::S3::PublicAccessBlock Account Wide Setting #168

leozhad opened this issue Sep 10, 2019 · 6 comments
Labels
Coverage enhancement New feature or request

Comments

@leozhad
Copy link

leozhad commented Sep 10, 2019

Instructions for CloudFormation Coverage New Issues Template

Quick Summary:

  1. Title -> AWS::Service::Resource-Attribute-Existing Attribute
  2. Scope of request -> AWS::S3::Bucket PublicAccessBlockConfiguration supports the setting at the bucket level today, but not the account level
  3. Expected behavior -> There should be a resource for turning on Public Access Block for a whole account in CloudFormation
  4. Category tag (optional) -> Storage
  5. Any additional context (optional)
@pamu78
Copy link

pamu78 commented Aug 22, 2020

any news on this?

@eduardomourar
Copy link

eduardomourar commented Sep 3, 2020

The private resource type Community::S3::PublicAccessBlock can be used in the meantime.

Installation instructions:

aws cloudformation register-type \
  --region us-east-1 \
  --type-name "Community::S3::PublicAccessBlock" \
  --schema-handler-package "s3://community-resource-provider-catalog/community-s3-publicaccessblock-0.1.0.zip" \
  --type RESOURCE \
  --execution-role-arn <ROLE_ARN_WITH_ENOUGH_PRIVILEGE>

Usage example:

AWSTemplateFormatVersion: 2010-09-09
Resources:
  S3AccountPublicAccessBlock:
    Type: 'Community::S3::PublicAccessBlock'
    Properties:
      BlockPublicAcls: true
      BlockPublicPolicy: false
      IgnorePublicAcls: true
      RestrictPublicBuckets: true

@dlenski
Copy link

dlenski commented Dec 23, 2020

This is a frustrating gap in CloudFormation.

As AWS's infrastructure-as-code tool, CloudFormation should be able to build all desired AWS infrastructure in a brand new AWS account using code, right?

But the inability to disable this account-wide feature via officially-supported CFN resource types means that CFN cannot do that when those resources include S3 buckets with public access. 😝

The error message associated with violating this account-wide public access block is also quite unhelpful:

  "MyBucket": {
    "Type": "AWS::S3::Bucket"
  }
  "BucketPolicy": {
    "Type": "AWS::S3::BucketPolicy",
    "Properties": {
      "Bucket": {"Ref": "MyBucket"},
      "PolicyDocument": {
        "Statement":[
          {
            "Action":["s3:GetObject"],
            "Effect":"Allow",
            "Resource": { "Fn::Sub" : "${MyBucket.Arn}/*" },
            "Principal": "*"
          }
        ]
      }  
    }    
  }      

Attempting to create this stack results in the error API: s3:PutBucketPolicy Access Denied. Because of this error, I spent a bunch of time trying to figure out if there was some race condition in the order of creation of the bucket and the policy, or some scenario in which the owner of an S3 bucket can't put a policy to the bucket they just created… when in fact neither of those had anything to do with the problem.

@WaelA WaelA added Coverage enhancement New feature or request labels Aug 3, 2021
@WaelA WaelA changed the title AWS::S3::Public Access Block Account Wide Setting AWS::S3::PublicAccessBlock Account Wide Setting Aug 5, 2021
@georgealton
Copy link

Just to add, this would be a great addition to use in StackSets applied to an Organization. All new and existing accounts could get BlockPublicAccess as a security baseline.

@alextongme
Copy link

alextongme commented May 5, 2022

any updates on this? ive found some workarounds but wondering if theres something in the works to make this a lot easier

@stewartcampbell
Copy link

This can be deployed as a stack set:

AWSTemplateFormatVersion: "2010-09-09"
Description: "Security: S3 Public Access Block Configuration"

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Lambda Function Configuration"
        Parameters:
          - LoggingLevel
      - Label:
          default: "S3 Public Access Block Configuration"
        Parameters:
          - BlockPublicAcls
          - BlockPublicPolicy
          - IgnorePublicAcls
          - RestrictPublicBuckets
    ParameterLabels:
      BlockPublicAcls:
        default: "Block Public ACLs"
      BlockPublicPolicy:
        default: "Block Public Policy"
      IgnorePublicAcls:
        default: "Ignore Public ACLs"
      LoggingLevel:
        default: "Logging Level"
      RestrictPublicBuckets:
        default: "Restrict Public Buckets"

Parameters:
  BlockPublicAcls:
    AllowedValues: ["True", "False"]
    Default: "True"
    Description: "S3 will block public access permissions applied to newly added buckets or objects, and prevent the creation of new public access ACLs for existing buckets and objects. This setting doesn't change any existing permissions that allow public access to S3 resources using ACLs."
    Type: String
  BlockPublicPolicy:
    AllowedValues: ["True", "False"]
    Default: "True"
    Description: "S3 will block new bucket and access point policies that grant public access to buckets and objects. This setting doesn't change any existing policies that allow public access to S3 resources."
    Type: String
  IgnorePublicAcls:
    AllowedValues: ["True", "False"]
    Default: "True"
    Description: "S3 will ignore all ACLs that grant public access to buckets and objects."
    Type: String
  LoggingLevel:
    AllowedValues: ["CRITICAL", "DEBUG", "ERROR", "INFO", "WARNING"]
    Default: "INFO"
    Description: "The logging level for the Lambda function."
    Type: String
  RestrictPublicBuckets:
    AllowedValues: ["True", "False"]
    Default: "True"
    Description: "S3 will ignore public and cross-account access for buckets or access points with policies that grant public access to buckets and objects."
    Type: String

Resources:
  LambdaExecutionRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: "sts:AssumeRole"
      Description: "Used by the S3 Public Access Block Lambda to update the account-level S3 public access block configuration."
      Policies:
        - PolicyName: LambdaExecutionPolicy
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "logs:CreateLogGroup"
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                Resource: "arn:aws:logs:*:*:*"
              - Effect: Allow
                Action:
                  - "s3:DeletePublicAccessBlock"
                  - "s3:PutAccountPublicAccessBlock"
                  - "sts:GetCallerIdentity"
                Resource: "*"
      RoleName: "s3-public-access-block-update"

  S3PublicAccessBlockLambda:
    Type: "AWS::Lambda::Function"
    Properties:
      Architectures:
        - x86_64
      Code:
        ZipFile: |
          import os
          import logging
          import boto3
          from botocore.exceptions import ClientError
          import cfnresponse
          logger = logging.getLogger()
          logger.setLevel(os.environ['LOGGING_LEVEL'])
          s3control_client = boto3.client("s3control")
          sts_client = boto3.client('sts')
          account_id = sts_client.get_caller_identity()["Account"]
          def lambda_handler(event, context):
              logger.info(f"Received event: {event}")
              physical_id = event['LogicalResourceId']
              try:
                  if event['RequestType'] in ['Create', 'Update']:
                      set_public_access_block(event['ResourceProperties'])
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, {}, physical_id)
                  elif event['RequestType'] == 'Delete':
                      delete_public_access_block()
                      cfnresponse.send(event, context, cfnresponse.SUCCESS, {}, physical_id)
                  else:
                      logger.error(f"Invalid request type: {event['RequestType']}")
                      cfnresponse.send(event, context, cfnresponse.FAILED, {}, physical_id)
              except Exception as e:
                  logger.error(f"Error: {str(e)}")
                  cfnresponse.send(event, context, cfnresponse.FAILED, {}, physical_id)
          def set_public_access_block(properties):
              try:
                  s3control_client.put_public_access_block(
                      PublicAccessBlockConfiguration={
                          'BlockPublicAcls': properties['BlockPublicAcls'].lower() == 'true',
                          'BlockPublicPolicy': properties['BlockPublicPolicy'].lower() == 'true',
                          'IgnorePublicAcls': properties['IgnorePublicAcls'].lower() == 'true',
                          'RestrictPublicBuckets': properties['RestrictPublicBuckets'].lower() == 'true'
                      },
                      AccountId=account_id
                  )
                  logger.info("S3 Public Access Block configuration updated successfully")
              except ClientError as e:
                  logger.error(f"Error updating S3 Public Access Block configuration: {str(e)}")
                  raise
          def delete_public_access_block():
              try:
                  s3control_client.delete_public_access_block(AccountId=account_id)
                  logger.info("S3 Public Access Block configuration deleted successfully")
              except ClientError as e:
                  if e.response['Error']['Code'] == 'NoSuchPublicAccessBlockConfiguration':
                      logger.info("S3 Public Access Block configuration was already deleted or did not exist")
                  else:
                      logger.error(f"Error deleting S3 Public Access Block configuration: {str(e)}")
                      raise
      Description: "Updates and deletes the account-level S3 Public Access Block configuration."
      Environment:
        Variables:
          LOGGING_LEVEL: !Ref LoggingLevel
      FunctionName: "s3-public-access-block-update"
      Handler: index.lambda_handler
      LoggingConfig:
        LogGroup: !Ref S3PublicAccessBlockLambdaLogGroup
      MemorySize: 128
      PackageType: Zip
      Role: !GetAtt LambdaExecutionRole.Arn
      Runtime: python3.12
      Timeout: 10

  S3PublicAccessBlockLambdaLogGroup:
    Type: "AWS::Logs::LogGroup"
    DeletionPolicy: Delete
    UpdateReplacePolicy: Delete
    Properties:
      RetentionInDays: 7

  S3PublicAccessBlock:
    Type: "Custom::S3PublicAccessBlock"
    Properties:
      BlockPublicAcls: !Ref BlockPublicAcls
      BlockPublicPolicy: !Ref BlockPublicPolicy
      IgnorePublicAcls: !Ref IgnorePublicAcls
      RestrictPublicBuckets: !Ref RestrictPublicBuckets
      ServiceToken: !GetAtt S3PublicAccessBlockLambda.Arn

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Coverage enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

8 participants