diff --git a/README.md b/README.md index cc438fea..4ceba773 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ These features of S3 bucket configurations are supported: - Cross-Region Replication (CRR) - ELB log delivery bucket policy - ALB/NLB log delivery bucket policy +- WAF log delivery bucket policy - Account-level Public Access Block - S3 Directory Bucket - S3 Table Bucket @@ -78,6 +79,24 @@ module "s3_bucket_for_logs" { } ``` +### Bucket with WAF log delivery policy attached + +```hcl +module "s3_bucket_for_waf_logs" { + source = "terraform-aws-modules/s3-bucket/aws" + + bucket = "my-s3-bucket-for-waf-logs" + + # Allow deletion of non-empty bucket + force_destroy = true + + control_object_ownership = true + object_ownership = "ObjectWriter" + + attach_waf_log_delivery_policy = true # Required for WAF logs +} +``` + ## Conditional creation Sometimes you need to have a way to create S3 resources conditionally but Terraform does not allow to use `count` inside `module` block, so the solution is to specify argument `create_bucket`. @@ -182,6 +201,7 @@ No modules. | [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 | | [aws_iam_policy_document.lb_log_delivery](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.require_latest_tls](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.waf_log_delivery](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | | [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | @@ -212,6 +232,7 @@ No modules. | [attach\_policy](#input\_attach\_policy) | Controls if S3 bucket should have bucket policy attached (set to `true` to use value of `policy` as bucket policy) | `bool` | `false` | no | | [attach\_public\_policy](#input\_attach\_public\_policy) | Controls if a user defined public bucket policy will be attached (set to `false` to allow upstream to apply defaults to the bucket) | `bool` | `true` | no | | [attach\_require\_latest\_tls\_policy](#input\_attach\_require\_latest\_tls\_policy) | Controls if S3 bucket should require the latest version of TLS | `bool` | `false` | no | +| [attach\_waf\_log\_delivery\_policy](#input\_attach\_waf\_log\_delivery\_policy) | Controls if S3 bucket should have WAF log delivery policy attached | `bool` | `false` | no | | [availability\_zone\_id](#input\_availability\_zone\_id) | Availability Zone ID or Local Zone ID | `string` | `null` | no | | [block\_public\_acls](#input\_block\_public\_acls) | Whether Amazon S3 should block public ACLs for this bucket. | `bool` | `true` | no | | [block\_public\_policy](#input\_block\_public\_policy) | Whether Amazon S3 should block public bucket policies for this bucket. | `bool` | `true` | no | diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 0a334919..91dc2d7e 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -75,6 +75,7 @@ module "log_bucket" { attach_access_log_delivery_policy = true attach_deny_insecure_transport_policy = true attach_require_latest_tls_policy = true + attach_waf_log_delivery_policy = true access_log_delivery_policy_source_accounts = [data.aws_caller_identity.current.account_id] access_log_delivery_policy_source_buckets = ["arn:aws:s3:::${local.bucket_name}"] diff --git a/main.tf b/main.tf index 7261524d..10f8fdb5 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_deny_ssec_encrypted_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 || var.attach_waf_log_delivery_policy # Variables with type `any` should be jsonencode()'d when value is coming from Terragrunt grants = try(jsondecode(var.grant), var.grant) @@ -576,7 +576,8 @@ data "aws_iam_policy_document" "combined" { 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 : "", - var.attach_policy ? var.policy : "" + var.attach_policy ? var.policy : "", + var.attach_waf_log_delivery_policy ? data.aws_iam_policy_document.waf_log_delivery[0].json : "", ]) } @@ -816,6 +817,79 @@ data "aws_iam_policy_document" "access_log_delivery" { } } +#WAF +data "aws_iam_policy_document" "waf_log_delivery" { + count = local.create_bucket && var.attach_waf_log_delivery_policy && !var.is_directory_bucket ? 1 : 0 + + statement { + sid = "AWSLogDeliveryWrite" + + effect = "Allow" + + principals { + type = "Service" + identifiers = ["delivery.logs.amazonaws.com"] + } + + actions = [ + "s3:PutObject", + ] + + resources = [ + "${aws_s3_bucket.this[0].arn}/AWSLogs/${data.aws_caller_identity.current.id}/*", + ] + + condition { + test = "StringEquals" + values = ["bucket-owner-full-control"] + variable = "s3:x-amz-acl" + } + + condition { + test = "StringEquals" + values = [data.aws_caller_identity.current.id] + variable = "aws:SourceAccount" + } + + condition { + test = "ArnLike" + values = ["arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.id}:*"] + variable = "aws:SourceArn" + } + } + + statement { + sid = "AWSLogDeliveryAclCheck" + + effect = "Allow" + + principals { + type = "Service" + identifiers = ["delivery.logs.amazonaws.com"] + } + + actions = [ + "s3:GetBucketAcl", + ] + + resources = [ + aws_s3_bucket.this[0].arn, + ] + + condition { + test = "StringEquals" + values = [data.aws_caller_identity.current.id] + variable = "aws:SourceAccount" + } + + condition { + test = "ArnLike" + values = ["arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.id}:*"] + variable = "aws:SourceArn" + } + } +} + data "aws_iam_policy_document" "deny_insecure_transport" { count = local.create_bucket && var.attach_deny_insecure_transport_policy && !var.is_directory_bucket ? 1 : 0 diff --git a/variables.tf b/variables.tf index b4201ce0..86e03e2a 100644 --- a/variables.tf +++ b/variables.tf @@ -88,6 +88,12 @@ variable "attach_deny_ssec_encrypted_object_uploads" { default = false } +variable "attach_waf_log_delivery_policy" { + description = "Controls if S3 bucket should have WAF log delivery policy attached" + 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 b21a4885..c38ab67d 100644 --- a/wrappers/main.tf +++ b/wrappers/main.tf @@ -26,6 +26,7 @@ module "wrapper" { attach_policy = try(each.value.attach_policy, var.defaults.attach_policy, false) attach_public_policy = try(each.value.attach_public_policy, var.defaults.attach_public_policy, true) attach_require_latest_tls_policy = try(each.value.attach_require_latest_tls_policy, var.defaults.attach_require_latest_tls_policy, false) + attach_waf_log_delivery_policy = try(each.value.attach_waf_log_delivery_policy, var.defaults.attach_waf_log_delivery_policy, false) availability_zone_id = try(each.value.availability_zone_id, var.defaults.availability_zone_id, null) block_public_acls = try(each.value.block_public_acls, var.defaults.block_public_acls, true) block_public_policy = try(each.value.block_public_policy, var.defaults.block_public_policy, true)