From e95e81a1247ba38b7011514b3b69c427393a5b59 Mon Sep 17 00:00:00 2001 From: Milan Zink Date: Thu, 8 Oct 2020 12:25:39 +0200 Subject: [PATCH 1/9] Provide support for AWS S3 Public Access Blocking Resolves issue #144 --- plugins/modules/s3_bucket.py | 74 +++++++++++++++++- tests/integration/targets/s3_bucket/inventory | 1 + .../roles/s3_bucket/tasks/public_access.yml | 77 +++++++++++++++++++ 3 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 tests/integration/targets/s3_bucket/roles/s3_bucket/tasks/public_access.yml diff --git a/plugins/modules/s3_bucket.py b/plugins/modules/s3_bucket.py index 2cc9021b6b5..a2e64eeaeec 100644 --- a/plugins/modules/s3_bucket.py +++ b/plugins/modules/s3_bucket.py @@ -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 @@ -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 @@ -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 = {} @@ -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) @@ -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): @@ -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): @@ -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( diff --git a/tests/integration/targets/s3_bucket/inventory b/tests/integration/targets/s3_bucket/inventory index 2968f764cfa..59a2423acdd 100644 --- a/tests/integration/targets/s3_bucket/inventory +++ b/tests/integration/targets/s3_bucket/inventory @@ -6,6 +6,7 @@ dotted tags encryption_kms encryption_sse +public_access [all:vars] ansible_connection=local diff --git a/tests/integration/targets/s3_bucket/roles/s3_bucket/tasks/public_access.yml b/tests/integration/targets/s3_bucket/roles/s3_bucket/tasks/public_access.yml new file mode 100644 index 00000000000..fd170d2c657 --- /dev/null +++ b/tests/integration/targets/s3_bucket/roles/s3_bucket/tasks/public_access.yml @@ -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 From 322f4be507b503e1f15d20e89d7d38ff3a6c1573 Mon Sep 17 00:00:00 2001 From: Milan Zink Date: Thu, 8 Oct 2020 12:46:33 +0200 Subject: [PATCH 2/9] Fix missing empty line --- plugins/modules/s3_bucket.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/modules/s3_bucket.py b/plugins/modules/s3_bucket.py index a2e64eeaeec..18621b98af8 100644 --- a/plugins/modules/s3_bucket.py +++ b/plugins/modules/s3_bucket.py @@ -520,6 +520,7 @@ 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): ''' From f5a21c0d67680465d04b9bb3f71703b2b8d63f79 Mon Sep 17 00:00:00 2001 From: Milan Zink Date: Thu, 8 Oct 2020 21:59:19 +0200 Subject: [PATCH 3/9] Chnages requested in pull request #171 --- plugins/modules/s3_bucket.py | 99 ++++++++++++------- .../roles/s3_bucket/tasks/public_access.yml | 61 +++++++++--- .../roles/s3_bucket/tasks/simple.yml | 3 +- 3 files changed, 117 insertions(+), 46 deletions(-) diff --git a/plugins/modules/s3_bucket.py b/plugins/modules/s3_bucket.py index 18621b98af8..271746db7f3 100644 --- a/plugins/modules/s3_bucket.py +++ b/plugins/modules/s3_bucket.py @@ -14,6 +14,9 @@ # along with this library. If not, see . from __future__ import (absolute_import, division, print_function) +import boto3 + +import botocore __metaclass__ = type @@ -92,10 +95,22 @@ 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' + - Suboptions [ + "block_public_acls", + "block_public_policy", + "ignore_public_acls", + "restrict_public_buckets" ] + - Allowed values 'true/false' + - Keys that are not explicitely defined defaults to 'false' + - This option cannot be used together with 'delete_public_access' type: dict + delete_public_access: + description: + - Delete public access block configuration from bucket + - This option cannot be used together with 'public_access' definition + default: false + type: bool + extends_documentation_fragment: - amazon.aws.aws - amazon.aws.ec2 @@ -161,7 +176,7 @@ state: present encryption: "aws:kms" -# Create a bucket with custom public policy block configuration +# Create a bucket with public policy block configuration - amazon.aws.s3_bucket: name: mys3bucket state: present @@ -171,6 +186,12 @@ ## keys == 'false' can be ommited, undefined keys defaults to 'false' # BlockPublicPolicy: false # RestrictPublicBuckets: false + +# Delete public policy block from bucket +- amazon.aws.s3_bucket: + name: mys3bucket + state: present + delete_public_access: true ''' import json @@ -194,6 +215,7 @@ from ..module_utils.ec2 import boto3_tag_list_to_ansible_dict from ..module_utils.ec2 import compare_policies from ..module_utils.ec2 import get_aws_connection_info +from ..module_utils.ec2 import snake_dict_to_camel_dict def create_or_update_bucket(s3_client, module, location): @@ -206,7 +228,8 @@ 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")) + public_access = module.params.get("public_access") + delete_public_access = module.params.get("delete_public_access") changed = False result = {} @@ -375,19 +398,31 @@ def create_or_update_bucket(s3_client, module, location): result['encryption'] = current_encryption - # Public access configuration + # Public access clock configuration + current_public_access = {} 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") + # -- Create / Update public access block if public_access is not None: - if current_public_access == public_access: + camel_public_block = snake_dict_to_camel_dict(public_access, capitalize_first=True) + if current_public_access == camel_public_block: + result['public_access_block'] = current_public_access + else: + put_bucket_public_access(s3_client, name, camel_public_block) + changed = True + result['public_access_block'] = camel_public_block + + # -- Delete public access block + if delete_public_access: + if current_public_access == {}: result['public_access_block'] = current_public_access else: - put_bucket_public_access(s3_client, name, public_access) + delete_bucket_public_access(s3_client, name) changed = True - result['public_access_block'] = public_access + result['public_access_block'] = {} # Module exit module.exit_json(changed=changed, name=name, **result) @@ -529,6 +564,14 @@ def put_bucket_public_access(s3_client, bucket_name, public_acces): s3_client.put_public_access_block(Bucket=bucket_name, PublicAccessBlockConfiguration=public_acces) +@AWSRetry.exponential_backoff(max_delay=120, catch_extra_error_codes=['NoSuchBucket', 'OperationAborted']) +def delete_bucket_public_access(s3_client, bucket_name): + ''' + Delete public access block from S3 bucket + ''' + s3_client.delete_public_access_block(Bucket=bucket_name) + + def wait_policy_is_applied(module, s3_client, bucket_name, expected_policy, should_fail=True): for dummy in range(0, 12): try: @@ -627,31 +670,12 @@ 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'] + bucket_public_access_block = s3_client.get_public_access_block(Bucket=bucket_name) + return bucket_public_access_block['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): @@ -764,15 +788,24 @@ def main(): ceph=dict(default=False, type='bool'), encryption=dict(choices=['none', 'AES256', 'aws:kms']), encryption_key_id=dict(), - public_access=dict(type='dict') - ) + public_access=dict(type='dict', options=dict( + block_public_acls=dict(type='bool', default=False), + ignore_public_acls=dict(type='bool', default=False), + block_public_policy=dict(type='bool', default=False), + restrict_public_buckets=dict(type='bool', default=False))), + delete_public_access=dict(type='bool', default=False) + ) required_by = dict( encryption_key_id=('encryption',), ) + mutually_exclusive = [ + ['public_access', 'delete_public_access'] + ] + module = AnsibleAWSModule( - argument_spec=argument_spec, required_by=required_by + argument_spec=argument_spec, required_by=required_by, mutually_exclusive=mutually_exclusive ) region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module, boto3=True) diff --git a/tests/integration/targets/s3_bucket/roles/s3_bucket/tasks/public_access.yml b/tests/integration/targets/s3_bucket/roles/s3_bucket/tasks/public_access.yml index fd170d2c657..f7bc19846e5 100644 --- a/tests/integration/targets/s3_bucket/roles/s3_bucket/tasks/public_access.yml +++ b/tests/integration/targets/s3_bucket/roles/s3_bucket/tasks/public_access.yml @@ -14,28 +14,39 @@ name: '{{ bucket_name }}' state: present public_access: - BlockPublicAcls: true - BlockPublicPolicy: true - IgnorePublicAcls: true - RestrictPublicBuckets: true + block_public_acls: true + block_public_policy: true + ignore_public_acls: true + restrict_public_buckets: true register: output + - assert: + that: + - output.changed + - output.public_access_block + - output.public_access_block.BlockPublicAcls + - output.public_access_block.BlockPublicPolicy + - output.public_access_block.IgnorePublicAcls + - output.public_access_block.RestrictPublicBuckets + - name: 'Re-configure public access block configuration' s3_bucket: name: '{{ bucket_name }}' state: present public_access: - BlockPublicAcls: true - BlockPublicPolicy: false - IgnorePublicAcls: true - RestrictPublicBuckets: false + block_public_acls: true + block_public_policy: false + ignore_public_acls: true + restrict_public_buckets: false register: output - assert: that: - output.changed - output.public_access_block + - output.public_access_block.BlockPublicAcls - not output.public_access_block.BlockPublicPolicy + - output.public_access_block.IgnorePublicAcls - not output.public_access_block.RestrictPublicBuckets - name: 'Re-configure public access block configuration (idempotency)' @@ -43,19 +54,45 @@ name: '{{ bucket_name }}' state: present public_access: - BlockPublicAcls: true - BlockPublicPolicy: false - IgnorePublicAcls: true - RestrictPublicBuckets: false + block_public_acls: true + block_public_policy: false + ignore_public_acls: true + restrict_public_buckets: false register: output - assert: that: - output is not changed - output.public_access_block + - output.public_access_block.BlockPublicAcls - not output.public_access_block.BlockPublicPolicy + - output.public_access_block.IgnorePublicAcls - not output.public_access_block.RestrictPublicBuckets + - name: 'Delete public access block configuration' + s3_bucket: + name: '{{ bucket_name }}' + state: present + delete_public_access: true + register: output + + - assert: + that: + - output is changed + - not output.public_access_block|bool + + - name: 'Delete public access block configuration (idempotency)' + s3_bucket: + name: '{{ bucket_name }}' + state: present + delete_public_access: true + register: output + + - assert: + that: + - output is not changed + - not output.public_access_block|bool + # ============================================================ - name: Delete testing s3 bucket diff --git a/tests/integration/targets/s3_bucket/roles/s3_bucket/tasks/simple.yml b/tests/integration/targets/s3_bucket/roles/s3_bucket/tasks/simple.yml index 3c39c5b4cb4..5b445bd5ee1 100644 --- a/tests/integration/targets/s3_bucket/roles/s3_bucket/tasks/simple.yml +++ b/tests/integration/targets/s3_bucket/roles/s3_bucket/tasks/simple.yml @@ -16,6 +16,7 @@ - output is changed - output.name == '{{ bucket_name }}' - not output.requester_pays + - output.public_access is undefined # ============================================================ - name: 'Try to update the simple bucket with the same values' @@ -44,7 +45,7 @@ - output is changed # ============================================================ - - name: 'Re-delete the simple s3_bucket (idemoptency)' + - name: 'Re-delete the simple s3_bucket (idempotency)' s3_bucket: name: '{{ bucket_name }}' state: absent From 7dcf2a1dd14696d695eff6b328201bd62be7f3f7 Mon Sep 17 00:00:00 2001 From: Milan Zink Date: Thu, 8 Oct 2020 22:05:06 +0200 Subject: [PATCH 4/9] Removing generated unused imports --- plugins/modules/s3_bucket.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugins/modules/s3_bucket.py b/plugins/modules/s3_bucket.py index 271746db7f3..88ec6661e92 100644 --- a/plugins/modules/s3_bucket.py +++ b/plugins/modules/s3_bucket.py @@ -14,9 +14,6 @@ # along with this library. If not, see . from __future__ import (absolute_import, division, print_function) -import boto3 - -import botocore __metaclass__ = type From 78f228ec0bdf46a551403edefc3f9753f6d7b6f2 Mon Sep 17 00:00:00 2001 From: Milan Zink Date: Thu, 8 Oct 2020 22:28:00 +0200 Subject: [PATCH 5/9] Fix suboptions doc and brackets alignment --- plugins/modules/s3_bucket.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/plugins/modules/s3_bucket.py b/plugins/modules/s3_bucket.py index 88ec6661e92..c507033abf6 100644 --- a/plugins/modules/s3_bucket.py +++ b/plugins/modules/s3_bucket.py @@ -92,15 +92,24 @@ public_access: description: - Configure public access block for S3 bucket - - Suboptions [ - "block_public_acls", - "block_public_policy", - "ignore_public_acls", - "restrict_public_buckets" ] - - Allowed values 'true/false' - - Keys that are not explicitely defined defaults to 'false' - This option cannot be used together with 'delete_public_access' - type: dict + suboptions: + block_public_acls: + description: Sets BlockPublicAcls value + type: bool + default: False + block_public_policy: + description: Sets BlockPublicPolicy value + type: bool + default: False + ignore_public_acls: + description: Sets IgnorePublicAcls value + type: bool + default: False + restrict_public_buckets: + description: Sets RestrictPublicAcls value + type: bool + default: False delete_public_access: description: - Delete public access block configuration from bucket @@ -791,7 +800,7 @@ def main(): block_public_policy=dict(type='bool', default=False), restrict_public_buckets=dict(type='bool', default=False))), delete_public_access=dict(type='bool', default=False) - ) + ) required_by = dict( encryption_key_id=('encryption',), From 3bcabc93b9ccbb18880b927a9b929a50dfce4c73 Mon Sep 17 00:00:00 2001 From: Milan Zink Date: Thu, 8 Oct 2020 22:38:57 +0200 Subject: [PATCH 6/9] Documentation --- plugins/modules/s3_bucket.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/modules/s3_bucket.py b/plugins/modules/s3_bucket.py index c507033abf6..0521d44d24c 100644 --- a/plugins/modules/s3_bucket.py +++ b/plugins/modules/s3_bucket.py @@ -110,6 +110,7 @@ description: Sets RestrictPublicAcls value type: bool default: False + type: dict delete_public_access: description: - Delete public access block configuration from bucket From 95e4b562f026d8a5fd3c0d92ad7db78f7ebcabb3 Mon Sep 17 00:00:00 2001 From: Milan Zink Date: Mon, 12 Oct 2020 08:39:58 +0200 Subject: [PATCH 7/9] Execute get_bucket_public_access only if required --- plugins/modules/s3_bucket.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/plugins/modules/s3_bucket.py b/plugins/modules/s3_bucket.py index 0521d44d24c..ef748550717 100644 --- a/plugins/modules/s3_bucket.py +++ b/plugins/modules/s3_bucket.py @@ -407,14 +407,15 @@ def create_or_update_bucket(s3_client, module, location): # Public access clock configuration current_public_access = {} - 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") # -- Create / Update public access block if public_access is not None: + 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") camel_public_block = snake_dict_to_camel_dict(public_access, capitalize_first=True) + if current_public_access == camel_public_block: result['public_access_block'] = current_public_access else: @@ -424,6 +425,11 @@ def create_or_update_bucket(s3_client, module, location): # -- Delete public access block if delete_public_access: + 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 current_public_access == {}: result['public_access_block'] = current_public_access else: From 276a0f08520c0090c5c1b94c57752dd5a6338b09 Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Mon, 16 Nov 2020 13:56:44 +0100 Subject: [PATCH 8/9] changelog --- changelogs/fragments/171-s3_bucket-public_access.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelogs/fragments/171-s3_bucket-public_access.yml diff --git a/changelogs/fragments/171-s3_bucket-public_access.yml b/changelogs/fragments/171-s3_bucket-public_access.yml new file mode 100644 index 00000000000..1a179a21e2c --- /dev/null +++ b/changelogs/fragments/171-s3_bucket-public_access.yml @@ -0,0 +1,2 @@ +minor_changes: +- s3_bucket - Add support for managing the ``public_access`` settings (https://github.com/ansible-collections/amazon.aws/pull/171). From 98092b32ac546e1880ccae24e82148514e91f378 Mon Sep 17 00:00:00 2001 From: Mark Chappell Date: Mon, 16 Nov 2020 14:23:43 +0100 Subject: [PATCH 9/9] Add missing version_added entries to doc --- plugins/modules/s3_bucket.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/modules/s3_bucket.py b/plugins/modules/s3_bucket.py index ef748550717..f04a6fb4a18 100644 --- a/plugins/modules/s3_bucket.py +++ b/plugins/modules/s3_bucket.py @@ -111,12 +111,14 @@ type: bool default: False type: dict + version_added: 1.3.0 delete_public_access: description: - Delete public access block configuration from bucket - This option cannot be used together with 'public_access' definition default: false type: bool + version_added: 1.3.0 extends_documentation_fragment: - amazon.aws.aws