Skip to content

Commit

Permalink
Merge pull request #446 from ministryofjustice/test/s3-replication
Browse files Browse the repository at this point in the history
Integrating the S3 bucket replication role module with the main S3 bucket repository
  • Loading branch information
Khatraf authored Jun 6, 2024
2 parents 08263e7 + 26143c6 commit cadab51
Show file tree
Hide file tree
Showing 9 changed files with 368 additions and 359 deletions.
28 changes: 16 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ module "s3-bucket" {
# Refer to the below section "Replication" before enabling replication
replication_enabled = false
# Below two variables and providers configuration are only relevant if 'replication_enabled' is set to true
# Below variable and providers configuration is only relevant if 'replication_enabled' is set to true
# replication_region = "eu-west-2"
# replication_role_arn = module.s3-bucket.role.arn
providers = {
# Here we use the default provider Region for replication. Destination buckets can be within the same Region as the
# source bucket. On the other hand, if you need to enable cross-region replication, please contact the Modernisation
Expand Down Expand Up @@ -88,7 +87,6 @@ module "s3-bucket" {
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | ~> 5.0 |
| <a name="provider_aws.bucket-replication"></a> [aws.bucket-replication](#provider\_aws.bucket-replication) | ~> 5.0 |
| <a name="provider_random"></a> [random](#provider\_random) | n/a |

## Modules

Expand All @@ -100,16 +98,18 @@ No modules.
|------|------|
| [aws_iam_policy.replication_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource |
| [aws_iam_role.replication_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
| [aws_iam_role_policy_attachment.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_iam_role_policy_attachment.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_s3_bucket.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
| [aws_s3_bucket.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
| [aws_s3_bucket_acl.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource |
| [aws_s3_bucket_acl.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource |
| [aws_s3_bucket_lifecycle_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource |
| [aws_s3_bucket_lifecycle_configuration.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource |
| [aws_s3_bucket_logging.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource |
| [aws_s3_bucket_notification.bucket_notification](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_notification) | resource |
| [aws_s3_bucket_notification.bucket_notification_replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_notification) | resource |
| [aws_s3_bucket_ownership_controls.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_ownership_controls) | resource |
| [aws_s3_bucket_ownership_controls.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_ownership_controls) | resource |
| [aws_s3_bucket_policy.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource |
| [aws_s3_bucket_policy.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource |
| [aws_s3_bucket_public_access_block.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
Expand All @@ -119,12 +119,11 @@ No modules.
| [aws_s3_bucket_server_side_encryption_configuration.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource |
| [aws_s3_bucket_versioning.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource |
| [aws_s3_bucket_versioning.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource |
| [random_string.s3_rnd](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource |
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
| [aws_iam_policy_document.bucket_policy_v2](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.default-policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.replication-policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.s3-assume-role-policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |

## Inputs
Expand All @@ -146,10 +145,12 @@ No modules.
| <a name="input_notification_events"></a> [notification\_events](#input\_notification\_events) | The event for which we send notifications | `list(string)` | <pre>[<br> ""<br>]</pre> | no |
| <a name="input_notification_sns_arn"></a> [notification\_sns\_arn](#input\_notification\_sns\_arn) | The arn for the bucket notification SNS topic | `string` | `""` | no |
| <a name="input_ownership_controls"></a> [ownership\_controls](#input\_ownership\_controls) | Bucket Ownership Controls - for use WITH acl var above options are 'BucketOwnerPreferred' or 'ObjectWriter'. To disable ACLs and use new AWS recommended controls set this to 'BucketOwnerEnforced' and which will disabled ACLs and ignore var.acl | `string` | `"ObjectWriter"` | no |
| <a name="input_replication_bucket"></a> [replication\_bucket](#input\_replication\_bucket) | Name of bucket used for replication - if not specified then * will be used in the policy | `string` | `""` | no |
| <a name="input_replication_enabled"></a> [replication\_enabled](#input\_replication\_enabled) | Activate S3 bucket replication | `bool` | `false` | no |
| <a name="input_replication_region"></a> [replication\_region](#input\_replication\_region) | Region to create S3 replication bucket | `string` | `"eu-west-2"` | no |
| <a name="input_replication_role_arn"></a> [replication\_role\_arn](#input\_replication\_role\_arn) | Role ARN to access S3 and replicate objects | `string` | `""` | no |
| <a name="input_sse_algorithm"></a> [sse\_algorithm](#input\_sse\_algorithm) | The server-side encryption algorithm to use | `string` | `"aws:kms"` | no |
| <a name="input_suffix_name"></a> [suffix\_name](#input\_suffix\_name) | Suffix for role and policy names | `string` | `""` | no |
| <a name="input_tags"></a> [tags](#input\_tags) | Tags to apply to resources, where applicable | `map(any)` | n/a | yes |
| <a name="input_versioning_enabled"></a> [versioning\_enabled](#input\_versioning\_enabled) | Activate S3 bucket versioning | `bool` | `true` | no |

Expand Down Expand Up @@ -180,14 +181,17 @@ Regardless of whether a custom bucket policy is set as part of this module, we w

If replication is enabled then:

- 'custom_replication_kms_key' variable is required, this key must allow access for S3
- 'versioning_enabled' variable must be set to enabled
- 'replication_role_arn' variable must be set to relevant arn for iam role
- Define a provider configuration for the replication region by setting 'aws.bucket-replication' to the desired region e.g.'aws.bucket-replication' = 'aws.replication-region'
- provide either'custom_replication_kms_key' or use default AWS KMS key. The KMS key must be in the same region as the destination bucket and must allow access for S3.
- 'versioning_enabled' variable must be set to enabled. Both source and destination buckets must have versioning enabled.
- 'replication_region' variable must be set to desired destination region.
- 'ownership_controls' variable must be set to 'BucketOwnerEnforced' for full control of all objects in the bucket and to disable ACLs.


There are two ways to create the IAM role for replication:

- use the [modernisation-platform-terraform-s3-bucket](https://github.com/ministryofjustice/modernisation-platform-terraform-s3-bucket) to configure a role based on bucket ARNs
- create one yourself, by following the [Setting up permissions for replication](https://docs.aws.amazon.com/AmazonS3/latest/dev/setting-repl-config-perm-overview.html) guide on AWS
- use the [modernisation-platform-terraform-s3-bucket](https://github.com/ministryofjustice/modernisation-platform-terraform-s3-bucket) to configure a role based on bucket ARNs.
- create one yourself, by following the [Setting up permissions for replication](https://docs.aws.amazon.com/AmazonS3/latest/dev/setting-repl-config-perm-overview.html) guide on AWS.

## Outputs

Expand Down Expand Up @@ -232,4 +236,4 @@ If you're looking to raise an issue with this module, please create a new issue
[SCA Icon]: https://img.shields.io/github/actions/workflow/status/ministryofjustice/modernisation-platform-terraform-s3-bucket/code-scanning.yml?branch=main&labelColor=231f20&style=for-the-badge&label=Secure%20Code%20Analysis
[SCA Link]: https://github.com/ministryofjustice/modernisation-platform-terraform-s3-bucket/actions/workflows/code-scanning.yml
[Terraform SCA Icon]: https://img.shields.io/github/actions/workflow/status/ministryofjustice/modernisation-platform-terraform-s3-bucket/code-scanning.yml?branch=main&labelColor=231f20&style=for-the-badge&label=Terraform%20Static%20Code%20Analysis
[Terraform SCA Link]: https://github.com/ministryofjustice/modernisation-platform-terraform-s3-bucket/actions/workflows/terraform-static-analysis.yml
[Terraform SCA Link]: https://github.com/ministryofjustice/modernisation-platform-terraform-s3-bucket/actions/workflows/terraform-static-analysis.yml
271 changes: 0 additions & 271 deletions main.tf
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
resource "random_string" "s3_rnd" {
length = 8
special = false
upper = false
}

data "aws_caller_identity" "current" {}

resource "aws_s3_bucket_notification" "bucket_notification" {
count = var.notification_enabled == true ? 1 : 0
bucket = aws_s3_bucket.default.id
Expand Down Expand Up @@ -116,7 +108,6 @@ resource "aws_s3_bucket_lifecycle_configuration" "default" {
}
}


# Configure bucket access logging
resource "aws_s3_bucket_logging" "default" {
for_each = (length(var.log_bucket) > 0) ? toset([var.log_bucket]) : []
Expand Down Expand Up @@ -144,34 +135,6 @@ resource "aws_s3_bucket_policy" "default" {
# Create the Public Access Block before the policy is added
depends_on = [aws_s3_bucket_public_access_block.default]
}

resource "aws_s3_bucket_replication_configuration" "default" {
for_each = var.replication_enabled ? toset(["run"]) : []
bucket = aws_s3_bucket.default.id
role = aws_iam_role.replication_role.arn

rule {
id = "default"
status = var.replication_enabled ? "Enabled" : "Disabled"
priority = 0

destination {
bucket = var.replication_enabled ? aws_s3_bucket.replication[0].arn : aws_s3_bucket.replication[0].arn
storage_class = "STANDARD"
encryption_configuration {
replica_kms_key_id = (var.custom_replication_kms_key != "") ? var.custom_replication_kms_key : "arn:aws:kms:${var.replication_region}:${data.aws_caller_identity.current.account_id}:alias/aws/s3"
}

}

source_selection_criteria {
sse_kms_encrypted_objects {
status = (var.custom_replication_kms_key != "") ? "Enabled" : "Disabled"
}
}
}
}

# AWS-provided KMS acceptable compromise in absence of customer provided key
# tfsec:ignore:aws-s3-encryption-customer-key
resource "aws_s3_bucket_server_side_encryption_configuration" "default" {
Expand Down Expand Up @@ -246,237 +209,3 @@ data "aws_iam_policy_document" "default" {
}
}

resource "aws_s3_bucket_notification" "bucket_notification_replication" {
count = var.replication_enabled && var.notification_enabled ? 1 : 0
bucket = aws_s3_bucket.replication[count.index]

topic {
topic_arn = var.notification_sns_arn
events = var.notification_events
}
}
# Replication S3 bucket, to replicate to (rather than from)
# Logging not deemed required for replication bucket
# tfsec:ignore:aws-s3-enable-bucket-logging
resource "aws_s3_bucket" "replication" {
#checkov:skip=CKV_AWS_144: "Replication not required on replication bucket"
#checkov:skip=CKV_AWS_18: "Logging handled in logging configuration resource"
#checkov:skip=CKV_AWS_21: "Versioning handled in versioning configuration resource"
#checkov:skip=CKV_AWS_145: "Encryption handled in encryption configuration resource"

count = var.replication_enabled ? 1 : 0

provider = aws.bucket-replication
bucket = (var.bucket_name != null) ? "${var.bucket_name}-replication" : null
bucket_prefix = (var.bucket_prefix != null) ? "${var.bucket_prefix}-replication" : null
force_destroy = var.force_destroy
tags = var.tags
}

# # Configure bucket ACL
# resource "aws_s3_bucket_acl" "replication" {
# count = var.replication_enabled ? 1 : 0

# provider = aws.bucket-replication
# bucket = aws_s3_bucket.replication[count.index].id
# acl = "private"
# }

# Configure bucket lifecycle rules

resource "aws_s3_bucket_lifecycle_configuration" "replication" {
#checkov:skip=CKV_AWS_300: "Ensure S3 lifecycle configuration sets period for aborting failed uploads"
count = var.replication_enabled ? 1 : 0

provider = aws.bucket-replication
bucket = aws_s3_bucket.replication[count.index].id
rule {
id = "main"
status = "Enabled"

transition {
days = 90
storage_class = "STANDARD_IA"
}

transition {
days = 365
storage_class = "GLACIER"
}

expiration {
days = 730
}

noncurrent_version_transition {
noncurrent_days = 90
storage_class = "STANDARD_IA"
}

noncurrent_version_transition {
noncurrent_days = 365
storage_class = "GLACIER"
}

noncurrent_version_expiration {
noncurrent_days = 730
}
}
}

# Block public access policies to the replication bucket
resource "aws_s3_bucket_public_access_block" "replication" {
count = var.replication_enabled ? 1 : 0

provider = aws.bucket-replication
bucket = aws_s3_bucket.replication[count.index].bucket
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}

# Attach policies to the S3 bucket
# This ensures every bucket created via this module
# doesn't allow any actions that aren't over SecureTransport methods (i.e. HTTP)
resource "aws_s3_bucket_policy" "replication" {
count = var.replication_enabled ? 1 : 0

provider = aws.bucket-replication
bucket = aws_s3_bucket.replication[count.index].id
policy = data.aws_iam_policy_document.replication[count.index].json

# Create the Public Access Block before the policy is added
depends_on = [aws_s3_bucket_public_access_block.replication]
}

resource "aws_s3_bucket_server_side_encryption_configuration" "replication" {
#checkov:skip=CKV2_AWS_67: "Ensure AWS S3 bucket encrypted with Customer Managed Key (CMK) has regular rotation"
count = var.replication_enabled ? 1 : 0

provider = aws.bucket-replication
bucket = aws_s3_bucket.replication[count.index].id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = var.sse_algorithm
kms_master_key_id = (var.custom_replication_kms_key != "") ? var.custom_replication_kms_key : ""
}
}
}

resource "aws_s3_bucket_versioning" "replication" {
count = var.replication_enabled ? 1 : 0

provider = aws.bucket-replication
bucket = aws_s3_bucket.replication[count.index].id
versioning_configuration {
status = (var.versioning_enabled != true) ? "Suspended" : "Enabled"
}
}

data "aws_iam_policy_document" "replication" {
count = var.replication_enabled ? 1 : 0

statement {
effect = "Deny"
actions = ["s3:*"]
resources = [
aws_s3_bucket.replication[count.index].arn,
"${aws_s3_bucket.replication[count.index].arn}/*"
]

principals {
identifiers = ["*"]
type = "AWS"
}

condition {
test = "Bool"
variable = "aws:SecureTransport"
values = ["false"]
}
}

}

# S3 bucket replication: role
resource "aws_iam_role" "replication_role" {
name = format("%s-%s", "AWSS3BucketReplication", random_string.s3_rnd.result)
assume_role_policy = data.aws_iam_policy_document.s3-assume-role-policy.json
tags = var.tags
}


# S3 bucket replication: assume role policy
data "aws_iam_policy_document" "s3-assume-role-policy" {
version = "2012-10-17"
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]

principals {
type = "Service"
identifiers = ["s3.amazonaws.com"]
}
}
}
resource "aws_iam_policy" "replication_policy" {
name = format("%s-%s", "AWSS3BucketReplicationPolicy", random_string.s3_rnd.result)
policy = data.aws_iam_policy_document.default-policy.json
}

# S3 bucket replication: role policy
# tfsec:ignore:aws-iam-no-policy-wildcards
data "aws_iam_policy_document" "default-policy" {
version = "2012-10-17"
statement {
effect = "Allow"
actions = [
"s3:GetReplicationConfiguration",
"s3:ListBucket"

]
resources = [aws_s3_bucket.default.arn]
}

statement {
effect = "Allow"
actions = [
"s3:GetObjectVersion",
"s3:GetObjectVersionAcl",
"s3:GetObjectVersionForReplication",
"s3:GetObjectLegalHold",
"s3:GetObjectRetention",
"s3:GetObjectVersionTagging"
]
resources = ["${aws_s3_bucket.default.arn}/*"]
}

statement {
effect = "Allow"
actions = [
"s3:ReplicateObject",
"s3:ReplicateDelete",
"s3:ReplicateTags",
"s3:GetObjectVersionTagging",
"s3:ObjectOwnerOverrideToBucketOwner"
]

# resources = [var.replication_enabled ? aws_s3_bucket.replication[0] : "*"]
resources = ["*"]

condition {
test = "StringLikeIfExists"
variable = "s3:x-amz-server-side-encryption"
values = [
"aws:kms",
"AES256"
]
}
}

}
resource "aws_iam_role_policy_attachment" "default" {
role = aws_iam_role.replication_role.name
policy_arn = aws_iam_policy.replication_policy.arn
}
Loading

0 comments on commit cadab51

Please sign in to comment.