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 2 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 to configure object ownership (https://github.com/ansible-collections/amazon.aws/pull/311)
87 changes: 84 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,23 @@
default: false
type: bool
version_added: 1.3.0
object_ownership:
description:
- Allow bucket's ownership controls.
- BucketOwnerPreferred : Objects uploaded to the bucket change ownership to the bucket owner
abikouo marked this conversation as resolved.
Show resolved Hide resolved
if the objects are uploaded with the bucket-owner-full-control canned ACL.
tremble marked this conversation as resolved.
Show resolved Hide resolved
- ObjectWriter: The uploading account will own the object
abikouo marked this conversation as resolved.
Show resolved Hide resolved
if the object is uploaded with the bucket-owner-full-control canned ACL.
tremble marked this conversation as resolved.
Show resolved Hide resolved
- 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
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.3.0
abikouo marked this conversation as resolved.
Show resolved Hide resolved

extends_documentation_fragment:
- amazon.aws.aws
Expand Down Expand Up @@ -202,6 +221,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 +271,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")
abikouo marked this conversation as resolved.
Show resolved Hide resolved
changed = False
result = {}

Expand Down Expand Up @@ -440,6 +473,25 @@ 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,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 != {} :
abikouo marked this conversation as resolved.
Show resolved Hide resolved
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



tremble marked this conversation as resolved.
Show resolved Hide resolved
# Module exit
module.exit_json(changed=changed, name=name, **result)

Expand Down Expand Up @@ -587,6 +639,23 @@ 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

abikouo 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):
Expand Down Expand Up @@ -691,6 +760,15 @@ def get_bucket_public_access(s3_client, bucket_name):
except is_boto3_error_code('NoSuchPublicAccessBlockConfiguration'):
return {}

def get_bucket_ownership_cntrl(s3_client,bucket_name) :
'''
Get current bucket public access block
'''
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
abikouo marked this conversation as resolved.
Show resolved Hide resolved

def paginated_list(s3_client, **pagination_params):
pg = s3_client.get_paginator('list_objects_v2')
Expand Down Expand Up @@ -809,15 +887,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)
abikouo marked this conversation as resolved.
Show resolved Hide resolved
)

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,98 @@
---
- 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: Ensure all buckets are deleted
s3_bucket:
name: '{{ bucket_name }}'
state: absent
ignore_errors: yes