Skip to content

Commit

Permalink
Provide support for AWS S3 Public Access Blocking
Browse files Browse the repository at this point in the history
  • Loading branch information
zeten30 committed Oct 8, 2020
1 parent 0fcc15c commit e95e81a
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 1 deletion.
74 changes: 73 additions & 1 deletion plugins/modules/s3_bucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@
description: KMS master key ID to use for the default encryption. This parameter is allowed if encryption is aws:kms. If
not specified then it will default to the AWS provided KMS key.
type: str
public_access:
description:
- Configure public access block for S3 bucket
- supported keys [ 'BlockPublicAcls', 'IgnorePublicAcls', 'BlockPublicPolicy', 'RestrictPublicBuckets' ]
- allowed values 'true/false'
- keys that are not explicitely defined defaults to 'false'
type: dict
extends_documentation_fragment:
- amazon.aws.aws
- amazon.aws.ec2
Expand Down Expand Up @@ -153,6 +160,17 @@
name: mys3bucket
state: present
encryption: "aws:kms"
# Create a bucket with custom public policy block configuration
- amazon.aws.s3_bucket:
name: mys3bucket
state: present
public_access:
BlockPublicAcls: true
IgnorePublicAcls: true
## keys == 'false' can be ommited, undefined keys defaults to 'false'
# BlockPublicPolicy: false
# RestrictPublicBuckets: false
'''

import json
Expand Down Expand Up @@ -188,6 +206,7 @@ def create_or_update_bucket(s3_client, module, location):
versioning = module.params.get("versioning")
encryption = module.params.get("encryption")
encryption_key_id = module.params.get("encryption_key_id")
public_access = sanitize_public_access_parameter(module.params.get("public_access"))
changed = False
result = {}

Expand Down Expand Up @@ -356,6 +375,21 @@ def create_or_update_bucket(s3_client, module, location):

result['encryption'] = current_encryption

# Public access configuration
try:
current_public_access = get_bucket_public_access(s3_client, name)
except (ClientError, BotoCoreError) as err_public_access:
module.fail_json_aws(err_public_access, msg="Failed to get bucket public access configuration")

if public_access is not None:
if current_public_access == public_access:
result['public_access_block'] = current_public_access
else:
put_bucket_public_access(s3_client, name, public_access)
changed = True
result['public_access_block'] = public_access

# Module exit
module.exit_json(changed=changed, name=name, **result)


Expand Down Expand Up @@ -486,6 +520,13 @@ def delete_bucket(s3_client, bucket_name):
# We just ignore the error
pass

@AWSRetry.exponential_backoff(max_delay=120, catch_extra_error_codes=['NoSuchBucket', 'OperationAborted'])
def put_bucket_public_access(s3_client, bucket_name, public_acces):
'''
Put new public access block to S3 bucket
'''
s3_client.put_public_access_block(Bucket=bucket_name, PublicAccessBlockConfiguration=public_acces)


def wait_policy_is_applied(module, s3_client, bucket_name, expected_policy, should_fail=True):
for dummy in range(0, 12):
Expand Down Expand Up @@ -580,6 +621,36 @@ def get_current_bucket_tags_dict(s3_client, bucket_name):
return boto3_tag_list_to_ansible_dict(current_tags)


def get_bucket_public_access(s3_client, bucket_name):
'''
Get current bucket public access block
'''
try:
current_public_access = s3_client.get_public_access_block(Bucket=bucket_name)
return current_public_access['PublicAccessBlockConfiguration']
except is_boto3_error_code('NoSuchPublicAccessBlockConfiguration'):
return {}


def sanitize_public_access_parameter(public_access_block):
'''
Sanitize public access block - make sure that only supported keys are defined with proper values
'''
sanitized_block = {'BlockPublicAcls': False, 'IgnorePublicAcls': False, 'BlockPublicPolicy': False, 'RestrictPublicBuckets': False}

if public_access_block is not None:
for key in public_access_block:
if str(key) in sanitized_block:
val = str(public_access_block[key]).lower()
if val == 'true':
sanitized_block[key] = True
else:
sanitized_block[key] = False
return sanitized_block
else:
return(None)


def paginated_list(s3_client, **pagination_params):
pg = s3_client.get_paginator('list_objects_v2')
for page in pg.paginate(**pagination_params):
Expand Down Expand Up @@ -691,7 +762,8 @@ def main():
versioning=dict(type='bool'),
ceph=dict(default=False, type='bool'),
encryption=dict(choices=['none', 'AES256', 'aws:kms']),
encryption_key_id=dict()
encryption_key_id=dict(),
public_access=dict(type='dict')
)

required_by = dict(
Expand Down
1 change: 1 addition & 0 deletions tests/integration/targets/s3_bucket/inventory
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ dotted
tags
encryption_kms
encryption_sse
public_access

[all:vars]
ansible_connection=local
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
- module_defaults:
group/aws:
aws_access_key: "{{ aws_access_key }}"
aws_secret_key: "{{ aws_secret_key }}"
security_token: "{{ security_token | default(omit) }}"
region: "{{ aws_region }}"
block:

# ============================================================

- name: 'Create a simple bucket with public access block configuration'
s3_bucket:
name: '{{ bucket_name }}'
state: present
public_access:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
register: output

- name: 'Re-configure public access block configuration'
s3_bucket:
name: '{{ bucket_name }}'
state: present
public_access:
BlockPublicAcls: true
BlockPublicPolicy: false
IgnorePublicAcls: true
RestrictPublicBuckets: false
register: output

- assert:
that:
- output.changed
- output.public_access_block
- not output.public_access_block.BlockPublicPolicy
- not output.public_access_block.RestrictPublicBuckets

- name: 'Re-configure public access block configuration (idempotency)'
s3_bucket:
name: '{{ bucket_name }}'
state: present
public_access:
BlockPublicAcls: true
BlockPublicPolicy: false
IgnorePublicAcls: true
RestrictPublicBuckets: false
register: output

- assert:
that:
- output is not changed
- output.public_access_block
- not output.public_access_block.BlockPublicPolicy
- not output.public_access_block.RestrictPublicBuckets

# ============================================================

- name: Delete testing s3 bucket
s3_bucket:
name: '{{ bucket_name }}'
state: absent
register: output

- assert:
that:
- output.changed

# ============================================================
always:
- name: Ensure all buckets are deleted
s3_bucket:
name: '{{ bucket_name }}'
state: absent
ignore_errors: yes

0 comments on commit e95e81a

Please sign in to comment.