From 773a0c11511a0dd2bcdf38a6acc51f326eee5c7c Mon Sep 17 00:00:00 2001 From: mbainter Date: Thu, 23 Jan 2025 12:03:33 -0600 Subject: [PATCH] feat: Add optional policy to deny ssec encrypted uploads There have been incidents where leaked credentials have led to attackers encrypting S3 buckets with their own keys in a form of ransomware attack. AWS recommends implementing policy to prevent using SSEC if that is not a feature that you use in a given bucket. Reference: https://aws.amazon.com/blogs/security/preventing-unintended-encryption-of-amazon-s3-objects/ --- README.md | 2 ++ examples/complete/main.tf | 17 +++++++++-------- main.tf | 31 ++++++++++++++++++++++++++++++- variables.tf | 6 ++++++ wrappers/main.tf | 1 + 5 files changed, 48 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b6eba6c0..0e4e2ef8 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,7 @@ No modules. | [aws_iam_policy_document.deny_incorrect_encryption_headers](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.deny_incorrect_kms_key_sse](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.deny_insecure_transport](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.deny_ssec_encrypted_object_uploads](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.deny_unencrypted_object_uploads](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.elb_log_delivery](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.inventory_and_analytics_destination_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | @@ -198,6 +199,7 @@ No modules. | [attach\_deny\_incorrect\_encryption\_headers](#input\_attach\_deny\_incorrect\_encryption\_headers) | Controls if S3 bucket should deny incorrect encryption headers policy attached. | `bool` | `false` | no | | [attach\_deny\_incorrect\_kms\_key\_sse](#input\_attach\_deny\_incorrect\_kms\_key\_sse) | Controls if S3 bucket policy should deny usage of incorrect KMS key SSE. | `bool` | `false` | no | | [attach\_deny\_insecure\_transport\_policy](#input\_attach\_deny\_insecure\_transport\_policy) | Controls if S3 bucket should have deny non-SSL transport policy attached | `bool` | `false` | no | +| [attach\_deny\_ssec\_encrypted\_object\_uploads](#input\_attach\_deny\_ssec\_encrypted\_object\_uploads) | Controls if S3 bucket should deny SSEC encrypted object uploads. | `bool` | `false` | no | | [attach\_deny\_unencrypted\_object\_uploads](#input\_attach\_deny\_unencrypted\_object\_uploads) | Controls if S3 bucket should deny unencrypted object uploads policy attached. | `bool` | `false` | no | | [attach\_elb\_log\_delivery\_policy](#input\_attach\_elb\_log\_delivery\_policy) | Controls if S3 bucket should have ELB log delivery policy attached | `bool` | `false` | no | | [attach\_inventory\_destination\_policy](#input\_attach\_inventory\_destination\_policy) | Controls if S3 bucket should have bucket inventory destination policy attached. | `bool` | `false` | no | diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 1f2538b7..59c8ee0e 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -139,14 +139,15 @@ module "s3_bucket" { } # Bucket policies - attach_policy = true - policy = data.aws_iam_policy_document.bucket_policy.json - attach_deny_insecure_transport_policy = true - attach_require_latest_tls_policy = true - attach_deny_incorrect_encryption_headers = true - attach_deny_incorrect_kms_key_sse = true - allowed_kms_key_arn = aws_kms_key.objects.arn - attach_deny_unencrypted_object_uploads = true + attach_policy = true + policy = data.aws_iam_policy_document.bucket_policy.json + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + attach_deny_incorrect_encryption_headers = true + attach_deny_incorrect_kms_key_sse = true + allowed_kms_key_arn = aws_kms_key.objects.arn + attach_deny_unencrypted_object_uploads = true + attach_deny_ssec_encrypted_object_uploads = true # S3 bucket-level Public Access Block configuration (by default now AWS has made this default as true for S3 bucket-level block public access) # block_public_acls = true diff --git a/main.tf b/main.tf index 6554c26c..ce49ab09 100644 --- a/main.tf +++ b/main.tf @@ -12,7 +12,7 @@ locals { create_bucket_acl = (var.acl != null && var.acl != "null") || length(local.grants) > 0 - attach_policy = var.attach_require_latest_tls_policy || var.attach_access_log_delivery_policy || var.attach_elb_log_delivery_policy || var.attach_lb_log_delivery_policy || var.attach_deny_insecure_transport_policy || var.attach_inventory_destination_policy || var.attach_deny_incorrect_encryption_headers || var.attach_deny_incorrect_kms_key_sse || var.attach_deny_unencrypted_object_uploads || var.attach_policy + attach_policy = var.attach_require_latest_tls_policy || var.attach_access_log_delivery_policy || var.attach_elb_log_delivery_policy || var.attach_lb_log_delivery_policy || var.attach_deny_insecure_transport_policy || var.attach_inventory_destination_policy || var.attach_deny_incorrect_encryption_headers || var.attach_deny_incorrect_kms_key_sse || var.attach_deny_unencrypted_object_uploads || var.attach_deny_ssec_encrypted_object_uploads || var.attach_policy # Variables with type `any` should be jsonencode()'d when value is coming from Terragrunt grants = try(jsondecode(var.grant), var.grant) @@ -558,6 +558,7 @@ data "aws_iam_policy_document" "combined" { var.attach_require_latest_tls_policy ? data.aws_iam_policy_document.require_latest_tls[0].json : "", var.attach_deny_insecure_transport_policy ? data.aws_iam_policy_document.deny_insecure_transport[0].json : "", var.attach_deny_unencrypted_object_uploads ? data.aws_iam_policy_document.deny_unencrypted_object_uploads[0].json : "", + var.attach_deny_ssec_encrypted_object_uploads ? data.aws_iam_policy_document.deny_ssec_encrypted_object_uploads[0].json : "", var.attach_deny_incorrect_kms_key_sse ? data.aws_iam_policy_document.deny_incorrect_kms_key_sse[0].json : "", var.attach_deny_incorrect_encryption_headers ? data.aws_iam_policy_document.deny_incorrect_encryption_headers[0].json : "", var.attach_inventory_destination_policy || var.attach_analytics_destination_policy ? data.aws_iam_policy_document.inventory_and_analytics_destination_policy[0].json : "", @@ -908,6 +909,34 @@ data "aws_iam_policy_document" "deny_unencrypted_object_uploads" { } } +data "aws_iam_policy_document" "deny_ssec_encrypted_object_uploads" { + count = local.create_bucket && var.attach_deny_ssec_encrypted_object_uploads ? 1 : 0 + + statement { + sid = "denySSECEncryptedObjectUploads" + effect = "Deny" + + actions = [ + "s3:PutObject" + ] + + resources = [ + "${aws_s3_bucket.this[0].arn}/*" + ] + + principals { + identifiers = ["*"] + type = "*" + } + + condition { + test = "Null" + variable = "s3:x-amz-server-side-encryption-customer-algorithm" + values = [false] + } + } +} + resource "aws_s3_bucket_public_access_block" "this" { count = local.create_bucket && var.attach_public_policy ? 1 : 0 diff --git a/variables.tf b/variables.tf index 92feec90..4d3aa95c 100644 --- a/variables.tf +++ b/variables.tf @@ -82,6 +82,12 @@ variable "attach_deny_unencrypted_object_uploads" { default = false } +variable "attach_deny_ssec_encrypted_object_uploads" { + description = "Controls if S3 bucket should deny SSEC encrypted object uploads." + type = bool + default = false +} + variable "bucket" { description = "(Optional, Forces new resource) The name of the bucket. If omitted, Terraform will assign a random, unique name." type = string diff --git a/wrappers/main.tf b/wrappers/main.tf index 5b3d0c72..d207d50e 100644 --- a/wrappers/main.tf +++ b/wrappers/main.tf @@ -17,6 +17,7 @@ module "wrapper" { attach_deny_incorrect_encryption_headers = try(each.value.attach_deny_incorrect_encryption_headers, var.defaults.attach_deny_incorrect_encryption_headers, false) attach_deny_incorrect_kms_key_sse = try(each.value.attach_deny_incorrect_kms_key_sse, var.defaults.attach_deny_incorrect_kms_key_sse, false) attach_deny_insecure_transport_policy = try(each.value.attach_deny_insecure_transport_policy, var.defaults.attach_deny_insecure_transport_policy, false) + attach_deny_ssec_encrypted_object_uploads = try(each.value.attach_deny_ssec_encrypted_object_uploads, var.defaults.attach_deny_ssec_encrypted_object_uploads, false) attach_deny_unencrypted_object_uploads = try(each.value.attach_deny_unencrypted_object_uploads, var.defaults.attach_deny_unencrypted_object_uploads, false) attach_elb_log_delivery_policy = try(each.value.attach_elb_log_delivery_policy, var.defaults.attach_elb_log_delivery_policy, false) attach_inventory_destination_policy = try(each.value.attach_inventory_destination_policy, var.defaults.attach_inventory_destination_policy, false)