@@ -8,68 +8,79 @@ data "aws_region" "current" {}
88locals {
99 bucket_name = coalesce (var. s3_bucket_name , " cur-csv-${ data . aws_caller_identity . current . account_id } " )
1010 bucket_arn = " arn:aws:s3:::${ local . bucket_name } "
11+ bucket_id = var. create_bucket ? module. cur_bucket [0 ]. s3_bucket_id : local. bucket_name
12+
13+ cur_s3_prefix = var. create_report ? aws_cur_report_definition. this [0 ]. s3_prefix : var. cur_s3_prefix
14+
15+ sns_topic_name = coalesce (var. sns_topic_name , " cur-notifications-${ data . aws_caller_identity . current . account_id } " )
1116}
1217
1318# =============================================================================
1419# IAM Policy Documents
1520# =============================================================================
1621
17- # Policy for AWS Billing service to write CUR reports + Lambda to read
22+ # Bucket policy: Billing service write + Lambda cross-account read
1823data "aws_iam_policy_document" "bucket_policy" {
1924 # Allow AWS Billing service to check bucket ACL
20- statement {
21- sid = " AllowBillingGetBucketAcl"
22- effect = " Allow"
23-
24- principals {
25- type = " Service"
26- identifiers = [" billingreports.amazonaws.com" ]
27- }
25+ dynamic "statement" {
26+ for_each = var. create_report ? [1 ] : []
27+ content {
28+ sid = " AllowBillingGetBucketAcl"
29+ effect = " Allow"
30+
31+ principals {
32+ type = " Service"
33+ identifiers = [" billingreports.amazonaws.com" ]
34+ }
2835
29- actions = [
30- " s3:GetBucketAcl" ,
31- " s3:GetBucketPolicy"
32- ]
36+ actions = [
37+ " s3:GetBucketAcl" ,
38+ " s3:GetBucketPolicy"
39+ ]
3340
34- resources = [local . bucket_arn ]
41+ resources = [local . bucket_arn ]
3542
36- condition {
37- test = " StringEquals"
38- variable = " aws:SourceArn"
39- values = [aws_cur_report_definition . this . arn ]
40- }
43+ condition {
44+ test = " StringEquals"
45+ variable = " aws:SourceArn"
46+ values = [aws_cur_report_definition . this [ 0 ] . arn ]
47+ }
4148
42- condition {
43- test = " StringEquals"
44- variable = " aws:SourceAccount"
45- values = [data . aws_caller_identity . current . account_id ]
49+ condition {
50+ test = " StringEquals"
51+ variable = " aws:SourceAccount"
52+ values = [data . aws_caller_identity . current . account_id ]
53+ }
4654 }
4755 }
4856
4957 # Allow AWS Billing service to write CUR reports
50- statement {
51- sid = " AllowBillingPutObject"
52- effect = " Allow"
53-
54- principals {
55- type = " Service"
56- identifiers = [" billingreports.amazonaws.com" ]
57- }
58+ dynamic "statement" {
59+ for_each = var. create_report ? [1 ] : []
60+ content {
61+ sid = " AllowBillingPutObject"
62+ effect = " Allow"
63+
64+ principals {
65+ type = " Service"
66+ identifiers = [" billingreports.amazonaws.com" ]
67+ }
5868
59- actions = [" s3:PutObject" ]
69+ actions = [" s3:PutObject" ]
6070
61- resources = [" ${ local . bucket_arn } /*" ]
71+ resources = [" ${ local . bucket_arn } /*" ]
6272
63- condition {
64- test = " StringEquals"
65- variable = " aws:SourceArn"
66- values = [aws_cur_report_definition . this . arn ]
67- }
73+ condition {
74+ test = " StringEquals"
75+ variable = " aws:SourceArn"
76+ values = [aws_cur_report_definition . this [ 0 ] . arn ]
77+ }
6878
69- condition {
70- test = " StringEquals"
71- variable = " aws:SourceAccount"
72- values = [data . aws_caller_identity . current . account_id ]
79+ condition {
80+ test = " StringEquals"
81+ variable = " aws:SourceAccount"
82+ values = [data . aws_caller_identity . current . account_id ]
83+ }
7384 }
7485 }
7586
@@ -97,13 +108,15 @@ data "aws_iam_policy_document" "bucket_policy" {
97108}
98109
99110# =============================================================================
100- # S3 Bucket for CUR reports
111+ # S3 Bucket for CUR reports (optional)
101112# =============================================================================
102113
103114module "cur_bucket" {
104115 source = " terraform-aws-modules/s3-bucket/aws"
105116 version = " 4.11.0"
106117
118+ count = var. create_bucket ? 1 : 0
119+
107120 bucket = local. bucket_name
108121
109122 versioning = {
@@ -168,19 +181,28 @@ module "cur_bucket" {
168181 })
169182}
170183
184+ # Attach bucket policy to existing bucket (when create_bucket = false)
185+ resource "aws_s3_bucket_policy" "existing" {
186+ count = var. create_bucket ? 0 : 1
187+
188+ bucket = local. bucket_name
189+ policy = data. aws_iam_policy_document . bucket_policy . json
190+ }
191+
171192# =============================================================================
172- # CUR Report Definition (must be in us-east-1)
193+ # CUR Report Definition (optional, must be in us-east-1)
173194# =============================================================================
174195
175196resource "aws_cur_report_definition" "this" {
197+ count = var. create_report ? 1 : 0
176198 provider = aws. us_east_1
177199
178200 report_name = " ${ lower (var. cur_time_unit )} -cur-${ lower (var. cur_format )} -${ data . aws_caller_identity . current . account_id } "
179201 time_unit = var. cur_time_unit
180202 format = var. cur_format
181203 compression = var. cur_compression
182204 additional_schema_elements = [" RESOURCES" , " SPLIT_COST_ALLOCATION_DATA" ]
183- s3_bucket = module . cur_bucket . s3_bucket_id
205+ s3_bucket = local . bucket_id
184206 s3_region = data. aws_region . current . name
185207 s3_prefix = " cur-reports"
186208
@@ -191,16 +213,102 @@ resource "aws_cur_report_definition" "this" {
191213}
192214
193215# =============================================================================
194- # S3 Event Notification to invoke Lambda in target account
216+ # SNS Topic for CUR notifications (optional)
217+ # =============================================================================
218+
219+ resource "aws_sns_topic" "cur" {
220+ count = var. use_sns ? 1 : 0
221+
222+ name = local. sns_topic_name
223+
224+ tags = merge (var. tags , {
225+ Name = " CUR Notifications"
226+ Purpose = " S3 event notifications for CUR reports"
227+ })
228+ }
229+
230+ data "aws_iam_policy_document" "sns_topic_policy" {
231+ count = var. use_sns ? 1 : 0
232+
233+ # Allow S3 to publish to SNS
234+ statement {
235+ sid = " AllowS3Publish"
236+ effect = " Allow"
237+
238+ principals {
239+ type = " Service"
240+ identifiers = [" s3.amazonaws.com" ]
241+ }
242+
243+ actions = [" SNS:Publish" ]
244+ resources = [aws_sns_topic . cur [0 ]. arn ]
245+
246+ condition {
247+ test = " ArnLike"
248+ variable = " aws:SourceArn"
249+ values = [local . bucket_arn ]
250+ }
251+
252+ condition {
253+ test = " StringEquals"
254+ variable = " aws:SourceAccount"
255+ values = [data . aws_caller_identity . current . account_id ]
256+ }
257+ }
258+
259+ # Allow target account Lambda/SQS to subscribe
260+ dynamic "statement" {
261+ for_each = length (var. sns_subscriber_arns ) > 0 ? [1 ] : []
262+ content {
263+ sid = " AllowCrossAccountSubscribe"
264+ effect = " Allow"
265+
266+ principals {
267+ type = " AWS"
268+ identifiers = var. sns_subscriber_arns
269+ }
270+
271+ actions = [
272+ " SNS:Subscribe" ,
273+ " SNS:Receive"
274+ ]
275+
276+ resources = [aws_sns_topic . cur [0 ]. arn ]
277+ }
278+ }
279+ }
280+
281+ resource "aws_sns_topic_policy" "cur" {
282+ count = var. use_sns ? 1 : 0
283+
284+ arn = aws_sns_topic. cur [0 ]. arn
285+ policy = data. aws_iam_policy_document . sns_topic_policy [0 ]. json
286+ }
287+
288+ # =============================================================================
289+ # S3 Event Notification
195290# =============================================================================
196- # Note: Lambda permission is managed in the target module
197291
198292resource "aws_s3_bucket_notification" "cur" {
199- bucket = module. cur_bucket . s3_bucket_id
293+ bucket = local. bucket_id
294+
295+ # Direct Lambda invocation
296+ dynamic "lambda_function" {
297+ for_each = var. use_sns ? [] : [1 ]
298+ content {
299+ lambda_function_arn = var. lambda_function_arn
300+ events = [" s3:ObjectCreated:*" ]
301+ filter_prefix = " ${ local . cur_s3_prefix } /"
302+ }
303+ }
200304
201- lambda_function {
202- lambda_function_arn = var. lambda_function_arn
203- events = [" s3:ObjectCreated:*" ]
204- filter_prefix = " ${ aws_cur_report_definition . this . s3_prefix } /"
305+ # SNS notification
306+ dynamic "topic" {
307+ for_each = var. use_sns ? [1 ] : []
308+ content {
309+ topic_arn = aws_sns_topic. cur [0 ]. arn
310+ events = [" s3:ObjectCreated:*" ]
311+ filter_prefix = " ${ local . cur_s3_prefix } /"
312+ }
205313 }
206314}
0 commit comments