From d8bc15daa736c0c2cbfe43bbc25f29dfb314f10b Mon Sep 17 00:00:00 2001 From: Soya Miyoshi Date: Mon, 4 Mar 2024 12:22:13 +0900 Subject: [PATCH] Added IP-based statement in bucket policy (#216) --- README.md | 1 + docs/terraform.md | 1 + main.tf | 22 ++++++++++++++++++++++ variables.tf | 7 +++++++ 4 files changed, 31 insertions(+) diff --git a/README.md b/README.md index f9a80d6a..d8981230 100644 --- a/README.md +++ b/README.md @@ -314,6 +314,7 @@ Available targets: | [s3\_replication\_permissions\_boundary\_arn](#input\_s3\_replication\_permissions\_boundary\_arn) | Permissions boundary ARN for the created IAM replication role. | `string` | `null` | no | | [s3\_replication\_rules](#input\_s3\_replication\_rules) | Specifies the replication rules for S3 bucket replication if enabled. You must also set s3\_replication\_enabled to true. |
list(object({
id = optional(string)
priority = optional(number)
prefix = optional(string)
status = optional(string, "Enabled")
# delete_marker_replication { status } had been flattened for convenience
delete_marker_replication_status = optional(string, "Disabled")
# Add the configuration as it appears in the resource, for consistency
# this nested version takes precedence if both are provided.
delete_marker_replication = optional(object({
status = string
}))

# destination_bucket is specified here rather than inside the destination object because before optional
# attributes, it made it easier to work with the Terraform type system and create a list of consistent type.
# It is preserved for backward compatibility, but the nested version takes priority if both are provided.
destination_bucket = optional(string) # destination bucket ARN, overrides s3_replica_bucket_arn

destination = object({
bucket = optional(string) # destination bucket ARN, overrides s3_replica_bucket_arn
storage_class = optional(string, "STANDARD")
# replica_kms_key_id at this level is for backward compatibility, and is overridden by the one in `encryption_configuration`
replica_kms_key_id = optional(string, "")
encryption_configuration = optional(object({
replica_kms_key_id = string
}))
access_control_translation = optional(object({
owner = string
}))
# account_id is for backward compatibility, overridden by account
account_id = optional(string)
account = optional(string)
# For convenience, specifying either metrics or replication_time enables both
metrics = optional(object({
event_threshold = optional(object({
minutes = optional(number, 15) # Currently 15 is the only valid number
}), { minutes = 15 })
status = optional(string, "Enabled")
}), { status = "Disabled" })
# To preserve backward compatibility, Replication Time Control (RTC) is automatically enabled
# when metrics are enabled. To enable metrics without RTC, you must explicitly configure
# replication_time.status = "Disabled".
replication_time = optional(object({
time = optional(object({
minutes = optional(number, 15) # Currently 15 is the only valid number
}), { minutes = 15 })
status = optional(string)
}))
})

source_selection_criteria = optional(object({
replica_modifications = optional(object({
status = string # Either Enabled or Disabled
}))
sse_kms_encrypted_objects = optional(object({
status = optional(string)
}))
}))
# filter.prefix overrides top level prefix
filter = optional(object({
prefix = optional(string)
tags = optional(map(string), {})
}))
}))
| `null` | no | | [s3\_replication\_source\_roles](#input\_s3\_replication\_source\_roles) | Cross-account IAM Role ARNs that will be allowed to perform S3 replication to this bucket (for replication within the same AWS account, it's not necessary to adjust the bucket policy). | `list(string)` | `[]` | no | +| [source\_ip\_allow\_list](#input\_source\_ip\_allow\_list) | List of IP addresses to allow to perform all actions to the bucket | `list(string)` | `[]` | no | | [source\_policy\_documents](#input\_source\_policy\_documents) | List of IAM policy documents (in JSON) that are merged together into the exported document.
Statements defined in source\_policy\_documents must have unique SIDs.
Statement having SIDs that match policy SIDs generated by this module will override them. | `list(string)` | `[]` | no | | [sse\_algorithm](#input\_sse\_algorithm) | The server-side encryption algorithm to use. Valid values are `AES256` and `aws:kms` | `string` | `"AES256"` | no | | [ssm\_base\_path](#input\_ssm\_base\_path) | The base path for SSM parameters where created IAM user's access key is stored | `string` | `"/s3_user/"` | no | diff --git a/docs/terraform.md b/docs/terraform.md index 3ff1e53a..77260f0c 100644 --- a/docs/terraform.md +++ b/docs/terraform.md @@ -99,6 +99,7 @@ | [s3\_replication\_permissions\_boundary\_arn](#input\_s3\_replication\_permissions\_boundary\_arn) | Permissions boundary ARN for the created IAM replication role. | `string` | `null` | no | | [s3\_replication\_rules](#input\_s3\_replication\_rules) | Specifies the replication rules for S3 bucket replication if enabled. You must also set s3\_replication\_enabled to true. |
list(object({
id = optional(string)
priority = optional(number)
prefix = optional(string)
status = optional(string, "Enabled")
# delete_marker_replication { status } had been flattened for convenience
delete_marker_replication_status = optional(string, "Disabled")
# Add the configuration as it appears in the resource, for consistency
# this nested version takes precedence if both are provided.
delete_marker_replication = optional(object({
status = string
}))

# destination_bucket is specified here rather than inside the destination object because before optional
# attributes, it made it easier to work with the Terraform type system and create a list of consistent type.
# It is preserved for backward compatibility, but the nested version takes priority if both are provided.
destination_bucket = optional(string) # destination bucket ARN, overrides s3_replica_bucket_arn

destination = object({
bucket = optional(string) # destination bucket ARN, overrides s3_replica_bucket_arn
storage_class = optional(string, "STANDARD")
# replica_kms_key_id at this level is for backward compatibility, and is overridden by the one in `encryption_configuration`
replica_kms_key_id = optional(string, "")
encryption_configuration = optional(object({
replica_kms_key_id = string
}))
access_control_translation = optional(object({
owner = string
}))
# account_id is for backward compatibility, overridden by account
account_id = optional(string)
account = optional(string)
# For convenience, specifying either metrics or replication_time enables both
metrics = optional(object({
event_threshold = optional(object({
minutes = optional(number, 15) # Currently 15 is the only valid number
}), { minutes = 15 })
status = optional(string, "Enabled")
}), { status = "Disabled" })
# To preserve backward compatibility, Replication Time Control (RTC) is automatically enabled
# when metrics are enabled. To enable metrics without RTC, you must explicitly configure
# replication_time.status = "Disabled".
replication_time = optional(object({
time = optional(object({
minutes = optional(number, 15) # Currently 15 is the only valid number
}), { minutes = 15 })
status = optional(string)
}))
})

source_selection_criteria = optional(object({
replica_modifications = optional(object({
status = string # Either Enabled or Disabled
}))
sse_kms_encrypted_objects = optional(object({
status = optional(string)
}))
}))
# filter.prefix overrides top level prefix
filter = optional(object({
prefix = optional(string)
tags = optional(map(string), {})
}))
}))
| `null` | no | | [s3\_replication\_source\_roles](#input\_s3\_replication\_source\_roles) | Cross-account IAM Role ARNs that will be allowed to perform S3 replication to this bucket (for replication within the same AWS account, it's not necessary to adjust the bucket policy). | `list(string)` | `[]` | no | +| [source\_ip\_allow\_list](#input\_source\_ip\_allow\_list) | List of IP addresses to allow to perform all actions to the bucket | `list(string)` | `[]` | no | | [source\_policy\_documents](#input\_source\_policy\_documents) | List of IAM policy documents (in JSON) that are merged together into the exported document.
Statements defined in source\_policy\_documents must have unique SIDs.
Statement having SIDs that match policy SIDs generated by this module will override them. | `list(string)` | `[]` | no | | [sse\_algorithm](#input\_sse\_algorithm) | The server-side encryption algorithm to use. Valid values are `AES256` and `aws:kms` | `string` | `"AES256"` | no | | [ssm\_base\_path](#input\_ssm\_base\_path) | The base path for SSM parameters where created IAM user's access key is stored | `string` | `"/s3_user/"` | no | diff --git a/main.tf b/main.tf index 371dad6c..cb2bf133 100644 --- a/main.tf +++ b/main.tf @@ -474,6 +474,28 @@ data "aws_iam_policy_document" "bucket_policy" { } } } + + dynamic "statement" { + for_each = length(var.source_ip_allow_list) > 0 ? [1] : [] + + content { + sid = "AllowIPPrincipals" + effect = "Deny" + actions = ["s3:*"] + resources = [local.bucket_arn, "${local.bucket_arn}/*"] + principals { + identifiers = ["*"] + type = "*" + } + condition { + test = "NotIpAddress" + variable = "aws:SourceIp" + values = var.source_ip_allow_list + } + } + + } + } data "aws_iam_policy_document" "aggregated_policy" { diff --git a/variables.tf b/variables.tf index f9511615..4191f1cc 100644 --- a/variables.tf +++ b/variables.tf @@ -411,6 +411,13 @@ variable "privileged_principal_actions" { nullable = false } +variable "source_ip_allow_list" { + type = list(string) + default = [] + description = "List of IP addresses to allow to perform all actions to the bucket" + nullable = false +} + variable "transfer_acceleration_enabled" { type = bool default = false