@@ -63,7 +63,6 @@ resource "aws_subnet" "private" {
6363# resilient, cost-effective, and simpler to manage than a per-AZ NAT Gateway.
6464
6565resource "aws_eip" "nat" {
66- # Only one EIP is needed for the single NAT Gateway.
6766 tags = merge (
6867 var. tags ,
6968 {
@@ -73,8 +72,6 @@ resource "aws_eip" "nat" {
7372}
7473
7574resource "aws_nat_gateway" "main" {
76- # Cost-optimized: a single NAT Gateway in the first public subnet.
77- # NOTE: This is a single-AZ SPOF for egress. A per-AZ NAT option could be added for higher availability.
7875 allocation_id = aws_eip. nat . id
7976 subnet_id = aws_subnet. public [0 ]. id
8077
@@ -145,11 +142,10 @@ data "aws_elb_service_account" "current" {}
145142data "aws_caller_identity" "current" {}
146143
147144# This is the primary bucket where the ALB will store its access logs.
148- resource "aws_s3_bucket" "alb_access_logs" {
149- # Only create this bucket if logging is enabled
150- count = var. enable_alb_access_logs ? 1 : 0
145+ # Only create this bucket if logging is enabled
151146
152- # If a specific name is provided, use it. Otherwise, generate a unique name.
147+ resource "aws_s3_bucket" "alb_access_logs" {
148+ count = var. enable_alb_access_logs ? 1 : 0
153149 bucket = var. alb_access_logs_bucket_name != " " ? var. alb_access_logs_bucket_name : " ${ var . project_prefix } -${ var . environment } -alb-access-logs-${ data . aws_caller_identity . current . account_id } "
154150
155151 tags = merge (
@@ -183,13 +179,133 @@ resource "aws_s3_bucket" "s3_server_access_logs" {
183179 }
184180 )
185181}
182+
183+
184+ resource "aws_s3_bucket_public_access_block" "s3_server_access_logs" {
185+ count = var. enable_alb_access_logs ? 1 : 0
186+ bucket = aws_s3_bucket. s3_server_access_logs [0 ]. id
187+
188+ block_public_acls = true
189+ block_public_policy = true
190+ ignore_public_acls = true
191+ restrict_public_buckets = true
192+ }
193+
194+ # Bucket Versioning
195+ resource "aws_s3_bucket_versioning" "alb_access_logs" {
196+ count = var. enable_alb_access_logs ? 1 : 0
197+ bucket = aws_s3_bucket. alb_access_logs [0 ]. id
198+
199+ versioning_configuration {
200+ status = " Enabled"
201+ }
202+ }
203+
204+ resource "aws_s3_bucket_versioning" "s3_server_access_logs" {
205+ count = var. enable_alb_access_logs ? 1 : 0
206+ bucket = aws_s3_bucket. s3_server_access_logs [0 ]. id
207+
208+ versioning_configuration {
209+ status = " Enabled"
210+ }
211+ }
212+
213+ # Buckets Encryption
214+
215+ resource "aws_s3_bucket_server_side_encryption_configuration" "alb_access_logs" {
216+ count = var. enable_alb_access_logs ? 1 : 0
217+ bucket = aws_s3_bucket. alb_access_logs [0 ]. id
218+
219+ rule {
220+ apply_server_side_encryption_by_default {
221+ sse_algorithm = " AES256"
222+ }
223+ bucket_key_enabled = true
224+ }
225+ }
226+
227+ resource "aws_s3_bucket_server_side_encryption_configuration" "s3_server_access_logs" {
228+ count = var. enable_alb_access_logs ? 1 : 0
229+ bucket = aws_s3_bucket. s3_server_access_logs [0 ]. id
230+
231+ rule {
232+ apply_server_side_encryption_by_default {
233+ sse_algorithm = " AES256"
234+ }
235+ bucket_key_enabled = true
236+ }
237+ }
238+
239+ # BUCKETS LIFECYCLE POLICIES
240+ resource "aws_s3_bucket_lifecycle_configuration" "alb_access_logs" {
241+ count = var. enable_alb_access_logs ? 1 : 0
242+ bucket = aws_s3_bucket. alb_access_logs [0 ]. id
243+
244+ rule {
245+ id = " expire-old-logs"
246+ status = " Enabled"
247+
248+ transition {
249+ days = 30
250+ storage_class = " STANDARD_IA"
251+ }
252+
253+ transition {
254+ days = 90
255+ storage_class = " GLACIER"
256+ }
257+
258+ expiration {
259+ days = 365
260+ }
261+
262+ noncurrent_version_expiration {
263+ noncurrent_days = 30
264+ }
265+ }
266+ }
267+
268+ resource "aws_s3_bucket_lifecycle_configuration" "s3_server_access_logs" {
269+ count = var. enable_alb_access_logs ? 1 : 0
270+ bucket = aws_s3_bucket. s3_server_access_logs [0 ]. id
271+
272+ rule {
273+ id = " expire-old-logs"
274+ status = " Enabled"
275+
276+ transition {
277+ days = 30
278+ storage_class = " STANDARD_IA"
279+ }
280+
281+ expiration {
282+ days = 180
283+ }
284+
285+ noncurrent_version_expiration {
286+ noncurrent_days = 30
287+ }
288+ }
289+ }
290+
291+
292+ resource "aws_s3_bucket_logging" "s3_server_access_logs" {
293+ count = var. enable_alb_access_logs ? 1 : 0
294+
295+ bucket = aws_s3_bucket. s3_server_access_logs [0 ]. id
296+
297+ # S3 server access logs bucket logs to itself in a different prefix
298+ target_bucket = aws_s3_bucket. s3_server_access_logs [0 ]. id
299+ target_prefix = " self-logs/"
300+ }
301+
186302resource "aws_s3_bucket_logging" "alb_access_logs" {
187303 count = var. enable_alb_access_logs ? 1 : 0
188304
189305 bucket = aws_s3_bucket. alb_access_logs [0 ]. id
190306
191307 target_bucket = aws_s3_bucket. s3_server_access_logs [0 ]. id
192- target_prefix = " log /"
308+ target_prefix = " alb-bucket-logs /"
193309}
194310
195311
@@ -199,7 +315,8 @@ data "aws_iam_policy_document" "alb_access_logs" {
199315
200316 # This statement allows the ALB service to write logs to the bucket.
201317 statement {
202- effect = " AllowALBToWriteLogs"
318+ sid = " AllowALBToWriteLogs"
319+ effect = " Allow"
203320 actions = [" s3:PutObject" ]
204321 resources = [" ${ aws_s3_bucket . alb_access_logs [0 ]. arn } /*" ]
205322
@@ -209,12 +326,27 @@ data "aws_iam_policy_document" "alb_access_logs" {
209326 }
210327 }
211328
329+ statement {
330+ sid = " AllowALBToGetBucketACL"
331+ effect = " Allow"
332+ actions = [" s3:GetBucketAcl" ]
333+ resources = [aws_s3_bucket . alb_access_logs [0 ]. arn ]
334+
335+ principals {
336+ type = " AWS"
337+ identifiers = [data . aws_elb_service_account . current . arn ]
338+ }
339+ }
340+
212341 # This statement denies any access to the bucket over insecure HTTP.
213342 statement {
214343 sid = " DenyInsecureTransport"
215344 effect = " Deny"
216345 actions = [" s3:*" ]
217- resources = [" ${ aws_s3_bucket . alb_access_logs [0 ]. arn } /*" ]
346+ resources = [
347+ aws_s3_bucket . alb_access_logs [0 ]. arn ,
348+ " ${ aws_s3_bucket . alb_access_logs [0 ]. arn } /*"
349+ ]
218350
219351 principals {
220352 type = " *"
@@ -229,13 +361,73 @@ data "aws_iam_policy_document" "alb_access_logs" {
229361 }
230362}
231363
364+ # Added bucket policy for S3 server access logs bucket to enforce HTTPS-only access
365+
366+ data "aws_iam_policy_document" "s3_server_access_logs" {
367+ count = var. enable_alb_access_logs ? 1 : 0
368+
369+ # Allow S3 logging service to write logs
370+ statement {
371+ sid = " S3ServerAccessLogsPolicy"
372+ effect = " Allow"
373+ principals {
374+ type = " Service"
375+ identifiers = [" logging.s3.amazonaws.com" ]
376+ }
377+ actions = [
378+ " s3:PutObject"
379+ ]
380+ resources = [" ${ aws_s3_bucket . s3_server_access_logs [0 ]. arn } /*" ]
381+
382+ condition {
383+ test = " StringEquals"
384+ variable = " aws:SourceAccount"
385+ values = [data . aws_caller_identity . current . account_id ]
386+ }
387+ }
388+
389+ # Deny any access over insecure HTTP
390+ statement {
391+ sid = " DenyInsecureTransport"
392+ effect = " Deny"
393+ actions = [" s3:*" ]
394+ resources = [
395+ aws_s3_bucket . s3_server_access_logs [0 ]. arn ,
396+ " ${ aws_s3_bucket . s3_server_access_logs [0 ]. arn } /*"
397+ ]
398+ principals {
399+ type = " *"
400+ identifiers = [" *" ]
401+ }
402+ condition {
403+ test = " Bool"
404+ variable = " aws:SecureTransport"
405+ values = [" false" ]
406+ }
407+ }
408+ }
409+
232410# This resource is needed to grant the ALB service permission to write to my S3 bucket.
233411resource "aws_s3_bucket_policy" "alb_access_logs" {
234412 count = var. enable_alb_access_logs ? 1 : 0
235413 bucket = aws_s3_bucket. alb_access_logs [0 ]. id
236414 policy = data. aws_iam_policy_document . alb_access_logs [0 ]. json
415+
416+ depends_on = [
417+ aws_s3_bucket_public_access_block . alb_access_logs
418+ ]
237419}
238420
421+ # Added bucket policy attachment for S3 server access logs bucket
422+ resource "aws_s3_bucket_policy" "s3_server_access_logs" {
423+ count = var. enable_alb_access_logs ? 1 : 0
424+ bucket = aws_s3_bucket. s3_server_access_logs [0 ]. id
425+ policy = data. aws_iam_policy_document . s3_server_access_logs [0 ]. json
426+
427+ depends_on = [
428+ aws_s3_bucket_public_access_block . s3_server_access_logs
429+ ]
430+ }
239431
240432# Application Load Balancer
241433
@@ -288,10 +480,14 @@ resource "aws_lb" "main" {
288480 # Deletion protection should be enabled via a variable for production.
289481 enable_deletion_protection = var. environment == " prod" ? true : false
290482
291- access_logs {
292- bucket = var. enable_alb_access_logs ? aws_s3_bucket. alb_access_logs [0 ]. bucket : null
293- enabled = var. enable_alb_access_logs
294- prefix = " alb-logs"
483+ # Proper conditional for access_logs block
484+ dynamic "access_logs" {
485+ for_each = var. enable_alb_access_logs ? [1 ] : []
486+ content {
487+ bucket = aws_s3_bucket. alb_access_logs [0 ]. bucket
488+ enabled = true
489+ prefix = " alb-logs"
490+ }
295491 }
296492
297493 tags = merge (
@@ -300,6 +496,10 @@ resource "aws_lb" "main" {
300496 Name = " ${ var . project_prefix } -${ var . environment } -alb"
301497 }
302498 )
499+
500+ depends_on = [
501+ aws_s3_bucket_policy . alb_access_logs
502+ ]
303503}
304504
305505resource "aws_lb_listener" "http" {
0 commit comments