Skip to content

Commit 2f44956

Browse files
committed
fix(s3):Re- Harden S3 bucket security to resolve all SonarQube findings
This commit resolves all SonarQube security hotspots for the S3 buckets by: - Adding Public Access Block configurations to both log buckets. - Enforcing HTTPS-only access via bucket policies on both buckets. - Enabling server-access logging for both buckets. - Adding versioning, encryption, and lifecycle policies for a complete production-ready configuration.
1 parent 2caacc7 commit 2f44956

File tree

1 file changed

+214
-14
lines changed
  • Terraform/modules/01-Network

1 file changed

+214
-14
lines changed

Terraform/modules/01-Network/main.tf

Lines changed: 214 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ resource "aws_subnet" "private" {
6363
# resilient, cost-effective, and simpler to manage than a per-AZ NAT Gateway.
6464

6565
resource "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

7574
resource "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" {}
145142
data "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+
186302
resource "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.
233411
resource "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

305505
resource "aws_lb_listener" "http" {

0 commit comments

Comments
 (0)