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

add object ownership controls options for s3 bucket #311

Merged
merged 22 commits into from
May 4, 2021
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- s3_bucket - add new option ``object_ownership`` to configure object ownership (https://github.com/ansible-collections/amazon.aws/pull/311)
93 changes: 90 additions & 3 deletions plugins/modules/s3_bucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
description:
- Manage S3 buckets in AWS, DigitalOcean, Ceph, Walrus, FakeS3 and StorageGRID.
requirements: [ boto3 ]
author: "Rob White (@wimnat)"
author:
- Rob White (@wimnat)
- Aubin Bikouo (@abikouo)
options:
force:
description:
Expand Down Expand Up @@ -120,6 +122,24 @@
default: false
type: bool
version_added: 1.3.0
object_ownership:
description:
- Allow bucket's ownership controls.
- C(BucketOwnerPreferred) - Objects uploaded to the bucket change ownership to the bucket owner
if the objects are uploaded with the bucket-owner-full-control canned ACL.
- C(ObjectWriter) - The uploading account will own the object
if the object is uploaded with the bucket-owner-full-control canned ACL.
- This option cannot be used together with a I(delete_object_ownership) definition.
choices: [ 'BucketOwnerPreferred', 'ObjectWriter' ]
type: str
abikouo marked this conversation as resolved.
Show resolved Hide resolved
version_added: 1.5.0
abikouo marked this conversation as resolved.
Show resolved Hide resolved
delete_object_ownership:
description:
- Delete bucket's ownership controls.
- This option cannot be used together with a I(object_ownership) definition.
default: false
type: bool
version_added: 1.5.0

extends_documentation_fragment:
- amazon.aws.aws
Expand Down Expand Up @@ -202,6 +222,18 @@
name: mys3bucket
state: present
delete_public_access: true

# Create a bucket with object ownership controls set to ObjectWriter
- amazon.aws.s3_bucket:
name: mys3bucket
state: present
object_ownership: ObjectWriter

# Delete onwership controls from bucket
- amazon.aws.s3_bucket:
name: mys3bucket
state: present
delete_object_ownership: true
'''

import json
Expand Down Expand Up @@ -240,6 +272,8 @@ def create_or_update_bucket(s3_client, module, location):
encryption_key_id = module.params.get("encryption_key_id")
public_access = module.params.get("public_access")
delete_public_access = module.params.get("delete_public_access")
delete_object_ownership = module.params.get("delete_object_ownership")
object_ownership = module.params.get("object_ownership")
changed = False
result = {}

Expand Down Expand Up @@ -440,6 +474,23 @@ def create_or_update_bucket(s3_client, module, location):
changed = True
result['public_access_block'] = {}

# -- Bucket ownership
bucket_ownership = get_bucket_ownership_cntrl(s3_client, module, name)
result['object_ownership'] = bucket_ownership
if delete_object_ownership or object_ownership is not None:
if delete_object_ownership:
# delete S3 buckect ownership
if bucket_ownership != {}:
delete_bucket_ownership(s3_client, name)
changed = True
result['object_ownership'] = None
else:
# update S3 bucket ownership
if bucket_ownership != object_ownership:
put_bucket_ownership(s3_client, name, object_ownership)
changed = True
result['object_ownership'] = object_ownership

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

Expand Down Expand Up @@ -588,6 +639,26 @@ def delete_bucket_public_access(s3_client, bucket_name):
s3_client.delete_public_access_block(Bucket=bucket_name)


@AWSRetry.exponential_backoff(max_delay=120, catch_extra_error_codes=['NoSuchBucket', 'OperationAborted'])
tremble marked this conversation as resolved.
Show resolved Hide resolved
def delete_bucket_ownership(s3_client, bucket_name):
'''
Delete bucket ownership controls from S3 bucket
'''
s3_client.delete_bucket_ownership_controls(Bucket=bucket_name)


@AWSRetry.exponential_backoff(max_delay=120, catch_extra_error_codes=['NoSuchBucket', 'OperationAborted'])
def put_bucket_ownership(s3_client, bucket_name, target):
'''
Put bucket ownership controls for S3 bucket
'''
s3_client.put_bucket_ownership_controls(
Bucket=bucket_name,
OwnershipControls={
'Rules': [{'ObjectOwnership': target}]
})
tremble marked this conversation as resolved.
Show resolved Hide resolved


def wait_policy_is_applied(module, s3_client, bucket_name, expected_policy, should_fail=True):
for dummy in range(0, 12):
try:
Expand Down Expand Up @@ -692,6 +763,19 @@ def get_bucket_public_access(s3_client, bucket_name):
return {}


def get_bucket_ownership_cntrl(s3_client, module, bucket_name):
'''
Get current bucket public access block
'''
if not module.botocore_at_least('1.8.11'):
return None
try:
abikouo marked this conversation as resolved.
Show resolved Hide resolved
bucket_ownership = s3_client.get_bucket_ownership_controls(Bucket=bucket_name)
return bucket_ownership['OwnershipControls']['Rules'][0]['ObjectOwnership']
except is_boto3_error_code(['OwnershipControlsNotFoundError', 'NoSuchOwnershipControls']):
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 @@ -809,15 +893,18 @@ def main():
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)
delete_public_access=dict(type='bool', default=False),
object_ownership=dict(type='str', choices=['BucketOwnerPreferred', 'ObjectWriter']),
delete_object_ownership=dict(type='bool', default=False),
)

required_by = dict(
encryption_key_id=('encryption',),
)

mutually_exclusive = [
['public_access', 'delete_public_access']
['public_access', 'delete_public_access'],
['delete_object_ownership', 'object_ownership']
]

module = AnsibleAWSModule(
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 @@ -7,6 +7,7 @@ tags
encryption_kms
encryption_sse
public_access
ownership_controls

[all:vars]
ansible_connection=local
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
---
- 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:

abikouo marked this conversation as resolved.
Show resolved Hide resolved
# ============================================================

- name: 'Create a simple bucket bad value for ownership controls'
s3_bucket:
name: '{{ bucket_name }}'
state: present
object_ownership: default
ignore_errors: true
register: output

- assert:
that:
- output.failed

- name: 'Create bucket with object_ownership set to object_writer'
s3_bucket:
name: '{{ bucket_name }}'
state: present
ignore_errors: true
register: output

- assert:
that:
- output.changed
- not output.object_ownership|bool

- name: delete s3 bucket
s3_bucket:
name: '{{ bucket_name }}'
state: absent

- name: 'create s3 bucket with object ownership controls'
s3_bucket:
name: '{{ bucket_name }}'
state: present
object_ownership: ObjectWriter
register: output

- assert:
that:
- output.changed
- output.object_ownership
- output.object_ownership == 'ObjectWriter'

- name: 'update s3 bucket ownership controls'
s3_bucket:
name: '{{ bucket_name }}'
state: present
object_ownership: BucketOwnerPreferred
register: output

- assert:
that:
- output.changed
- output.object_ownership
- output.object_ownership == 'BucketOwnerPreferred'

- name: 'test idempotency update s3 bucket ownership controls'
s3_bucket:
name: '{{ bucket_name }}'
state: present
object_ownership: BucketOwnerPreferred
register: output

- assert:
that:
- output.changed is false
- output.object_ownership
- output.object_ownership == 'BucketOwnerPreferred'

- name: 'delete s3 bucket ownership controls'
s3_bucket:
name: '{{ bucket_name }}'
state: present
delete_object_ownership: true
register: output

- assert:
that:
- output.changed
- not output.object_ownership|bool

# ============================================================
always:
abikouo marked this conversation as resolved.
Show resolved Hide resolved
- name: delete s3 bucket ownership controls
s3_bucket:
name: '{{ bucket_name }}'
state: present
delete_object_ownership: true
ignore_errors: yes

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