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