34
34
- "The prefix that should be prepended to the generated log files written to the target_bucket."
35
35
default: ""
36
36
type: str
37
+ target_access:
38
+ description:
39
+ - "Permissions type for log delivery"
40
+ default: policy
41
+ choices: [ 'policy', 'acl' ]
42
+ type: str
37
43
extends_documentation_fragment:
38
44
- amazon.aws.common.modules
39
45
- amazon.aws.region.modules
58
64
state: absent
59
65
"""
60
66
67
+ import json
68
+
61
69
try :
62
70
import botocore
63
71
except ImportError :
@@ -85,8 +93,16 @@ def compare_bucket_logging(bucket_logging, target_bucket, target_prefix):
85
93
return False
86
94
87
95
88
- def verify_acls (connection , module , target_bucket ):
96
+ def verify_acls (connection , module , bucket_name , target_access , target_bucket ):
97
+ current_obj_ownership = "BucketOwnerEnforced"
89
98
try :
99
+ current_ownership = connection .get_bucket_ownership_controls (aws_retry = True , Bucket = target_bucket )
100
+ rules = current_ownership ["OwnershipControls" ]["Rules" ]
101
+ for rule in rules :
102
+ if "ObjectOwnership" in rule :
103
+ current_obj_ownership = rule ["ObjectOwnership" ]
104
+ if target_access == "acl" and current_obj_ownership == "BucketOwnerEnforced" :
105
+ module .fail_json_aws (msg = "The access type is set to ACL but it is disabled on target bucket" )
90
106
current_acl = connection .get_bucket_acl (aws_retry = True , Bucket = target_bucket )
91
107
current_grants = current_acl ["Grants" ]
92
108
except is_boto3_error_code ("NoSuchBucket" ):
@@ -95,25 +111,36 @@ def verify_acls(connection, module, target_bucket):
95
111
botocore .exceptions .BotoCoreError ,
96
112
botocore .exceptions .ClientError ,
97
113
) as e : # pylint: disable=duplicate-except
98
- module .fail_json_aws (e , msg = "Failed to fetch target bucket ACL " )
114
+ module .fail_json_aws (e , msg = "Failed to fetch target bucket ownership " )
99
115
100
116
required_grant = {
101
117
"Grantee" : {"URI" : "http://acs.amazonaws.com/groups/s3/LogDelivery" , "Type" : "Group" },
102
118
"Permission" : "FULL_CONTROL" ,
103
119
}
104
120
105
- for grant in current_grants :
106
- if grant == required_grant :
121
+ updated_grants = []
122
+ if target_access == "acl" :
123
+ if required_grant not in list (current_grants ):
124
+ updated_grants = list (current_grants )
125
+ updated_grants .append (required_grant )
126
+ else :
127
+ return False
128
+ else :
129
+ if required_grant in list (current_grants ):
130
+ for grant in current_grants :
131
+ if grant != required_grant :
132
+ updated_grants .append (grant )
133
+ else :
107
134
return False
108
-
109
- if module .check_mode :
110
- return True
111
135
112
136
updated_acl = dict (current_acl )
113
- updated_grants = list (current_grants )
114
- updated_grants .append (required_grant )
115
137
updated_acl ["Grants" ] = updated_grants
116
138
del updated_acl ["ResponseMetadata" ]
139
+ module .warn (json .dumps (updated_acl ))
140
+
141
+ if module .check_mode :
142
+ return True
143
+
117
144
try :
118
145
connection .put_bucket_acl (aws_retry = True , Bucket = target_bucket , AccessControlPolicy = updated_acl )
119
146
except (botocore .exceptions .BotoCoreError , botocore .exceptions .ClientError ) as e :
@@ -122,10 +149,70 @@ def verify_acls(connection, module, target_bucket):
122
149
return True
123
150
124
151
125
- def enable_bucket_logging (connection , module ):
152
+ def verify_policy (connection , module , bucket_name , target_access , target_bucket , target_prefix ):
153
+ policy = {"Version" : "2012-10-17" , "Statement" : []}
154
+ policy_statement = {
155
+ "Sid" : bucket_name ,
156
+ "Effect" : "Allow" ,
157
+ "Principal" : {"Service" : "logging.s3.amazonaws.com" },
158
+ "Action" : "s3:PutObject" ,
159
+ "Resource" : f"arn:aws:s3:::{ target_bucket } /{ target_prefix } *" ,
160
+ }
161
+
162
+ try :
163
+ current_policy_raw = connection .get_bucket_policy (aws_retry = True , Bucket = target_bucket )
164
+ except is_boto3_error_code ("NoSuchBucket" ):
165
+ module .fail_json (msg = f"Target Bucket '{ target_bucket } ' not found" )
166
+ except is_boto3_error_code ("NoSuchBucketPolicy" ):
167
+ current_policy_raw = {"Policy" : json .dumps (policy )}
168
+ except (
169
+ botocore .exceptions .BotoCoreError ,
170
+ botocore .exceptions .ClientError ,
171
+ ) as e : # pylint: disable=duplicate-except
172
+ module .fail_json_aws (e , msg = "Failed to fetch target bucket policy" )
173
+
174
+ try :
175
+ current_policy = json .loads (current_policy_raw ["Policy" ])
176
+ except json .JSONDecodeError as e :
177
+ module .fail_json (e , msg = "Unable to parse policy" )
178
+
179
+ new_policy_statements = []
180
+ new_policy = current_policy
181
+ for p in current_policy .get ("Statement" , []):
182
+ if p == policy_statement and target_access == "policy" :
183
+ return False
184
+ if target_access == "acl" :
185
+ for p in current_policy .get ("Statement" , []):
186
+ if p != policy_statement :
187
+ new_policy_statements .append (p )
188
+ new_policy ["Statement" ] = new_policy_statements
189
+ if new_policy == current_policy :
190
+ return False
191
+ else :
192
+ new_policy = current_policy
193
+ if new_policy ["Statement" ] is None :
194
+ new_policy ["Statement" ] = [policy_statement ]
195
+ else :
196
+ new_policy ["Statement" ].append (policy_statement )
197
+
198
+ if module .check_mode :
199
+ return True
200
+
201
+ try :
202
+ connection .put_bucket_policy (aws_retry = True , Bucket = target_bucket , Policy = json .dumps (new_policy ))
203
+ except is_boto3_error_code ("MalformedPolicy" ):
204
+ module .fail_json (msg = f"Bad policy:\n { new_policy } " )
205
+ except (botocore .exceptions .BotoCoreError , botocore .exceptions .ClientError ) as e :
206
+ module .fail_json_aws (e , msg = "Failed to update target bucket policy to allow log delivery" )
207
+
208
+ return True
209
+
210
+
211
+ def enable_bucket_logging (connection , module : AnsibleAWSModule ):
126
212
bucket_name = module .params .get ("name" )
127
213
target_bucket = module .params .get ("target_bucket" )
128
214
target_prefix = module .params .get ("target_prefix" )
215
+ target_access = module .params .get ("target_access" )
129
216
changed = False
130
217
131
218
try :
@@ -139,7 +226,8 @@ def enable_bucket_logging(connection, module):
139
226
module .fail_json_aws (e , msg = "Failed to fetch current logging status" )
140
227
141
228
try :
142
- changed |= verify_acls (connection , module , target_bucket )
229
+ changed |= verify_acls (connection , module , bucket_name , target_access , target_bucket )
230
+ changed |= verify_policy (connection , module , bucket_name , target_access , target_bucket , target_prefix )
143
231
144
232
if not compare_bucket_logging (bucket_logging , target_bucket , target_prefix ):
145
233
bucket_logging = camel_dict_to_snake_dict (bucket_logging )
@@ -196,6 +284,7 @@ def main():
196
284
name = dict (required = True ),
197
285
target_bucket = dict (required = False , default = None ),
198
286
target_prefix = dict (required = False , default = "" ),
287
+ target_access = dict (required = False , default = "policy" , choices = ["policy" , "acl" ]),
199
288
state = dict (required = False , default = "present" , choices = ["present" , "absent" ]),
200
289
)
201
290
0 commit comments